【Algorithm】Algorithm Learning

Daily Algorithm Learning.
《靡不有初系列》

算法学习:

靡不有初系列

LC Daily Problem

525. 连续数组

前缀和+哈希+数学推导

image-20210603075345587

class Solution {
public:
    int findMaxLength(vector<int>& nums) {
        int n = nums.size();
        if(n <= 1) return 0;
        //[j, i]
        //2(pre[i+1] - pre[j]) = i+1 -j
        //2pre[i+1] - (i+1) = 2pre[j] - j;
        unordered_map<int, int> mp;
        int pre = 0, res = 0;
        mp[0] = 0;
        for(int i=0; i<n; ++i) {
            pre += nums[i];
            int now = 2*pre - (i+1);
            if(mp.count(now)) {
                res = max(res, i+1-mp[now]);
            } else {
                mp[now] = i+1;
            }
        }
        return res;
    }
};


官方题解

class Solution {
public:
    int findMaxLength(vector<int>& nums) {
        int maxLength = 0;
        unordered_map<int, int> mp;
        int counter = 0;
        mp[counter] = -1;
        int n = nums.size();
        for (int i = 0; i < n; i++) {
            int num = nums[i];
            if (num == 1) {
                counter++;
            } else {
                counter--;
            }
            if (mp.count(counter)) {
                int prevIndex = mp[counter];
                maxLength = max(maxLength, i - prevIndex);
            } else {
                mp[counter] = i;
            }
        }
        return maxLength;
    }
};

523. 连续的子数组和

数学题:

同余定理:如果A%k == 0, B % k == 0,那么(A - B)% k == 0

优化题:

剪枝

class Solution {
    public boolean checkSubarraySum(int[] nums, int k) {
        // 当出现两个连续的0时,直接返回true,因为 0 % k = 0 
        for (int i = 0; i < nums.length - 1; i++) {
            if (nums[i] == 0 && nums[i + 1] == 0) {
                return true;
            }
        }

        // 其中i为左端点,j为右端点,遍历每种情况
        for (int i = 0; i < nums.length; i++) {
            int sum = nums[i];
            for (int j = i + 1; j < nums.length; j++) {
                sum += nums[j];
                if (sum % k == 0) {
                    return true;
                }
            }
            // 加到一起之后发现都没k大,后面的也不会再比k大了,跳过
            if (sum < k) {
                break;
            }
        }
        return false;
    }
}

342. 4的幂

位运算

数学题

下标从0位开始,偶数位为4的幂,1010 = a

image-20210531211812809

func isPowerOfFour(n int) bool {
    return n > 0 && n&(n-1) == 0 && n&0xaaaaaaaa == 0
}

461. 汉明距离

内置函数

位运算

image-20210527080618332

go

func hammingDistance(x, y int) int {
    return bits.OnesCount(uint(x ^ y))
}

c++

class Solution {
public:
    int hammingDistance(int x, int y) {
        return __builtin_popcount(x ^ y);
    }
};
class Solution {
public:
    int hammingDistance(int x, int y) {
        int s = x ^ y, ret = 0;
        while (s) {
            s &= s - 1; // 消除最低位的1
            ret++;
        }
        return ret;
    }
};

1190. 反转每对括号间的子串

栈进行模拟

image-20210526080735421

class Solution {
public:
    string reverseParentheses(string s) {
        stack<string> stk;
        string str;
        for (auto &ch : s) {
            if (ch == '(') { // 左括号表示进入新一层 需要将之前的str保留 再与下一层作叠加
                stk.push(str);
                str = "";
            } else if (ch == ')') { // 已经到最里层 将最里层的字符串翻转 返回给上一层
                reverse(str.begin(), str.end());
                str = stk.top() + str;
                stk.pop();
            } else {
                str.push_back(ch);
            }
        }
        return str;
    }
};

664. 奇怪的打印机

C++区间dp

image-20210524084531147

class Solution {
public:
    int strangePrinter(string s) {
        int n = s.size();
        // vector<vector<int>> dp(n, vector<int>(n, 0x3f3f3f3f));
        int dp[n][n];
        memset(dp, 0x3f3f3f3f, sizeof(dp));

        for(int i = 0; i < n; i++)
            dp[i][i] = 1;
        
        for(int len = 2; len <= n; len++) {
            for(int i = 0, j = len-1; j < n; i++, j++) {
                if(s[i] == s[j])
                    dp[i][j] = min(dp[i+1][j], dp[i][j-1]);
                else {
                    for(int k = i; k < j; k++)
                        dp[i][j] = min(dp[i][j], dp[i][k]+dp[k+1][j]);
                }
            }  
        }
        return dp[0][n-1];
    }
};

1707. 与数组中元素的最大异或值

image-20210523092437245

超时做法:暴力+剪枝

class Solution {
public:
    vector<int> maximizeXor(vector<int>& nums, vector<vector<int>>& queries) {
        int n = nums.size();
        int m = queries.size();
        vector<int> ans(m);
        sort(nums.begin(), nums.end(), greater<int>());
        for (int i = 0; i < m; i++) {
            int MAX = 0;
            int x = queries[i][0], y = queries[i][1];
            int pos = lower_bound(nums.begin(),nums.end(), y, greater<int>()) - nums.begin();
            //cout << pos << endl;
            if (pos == n) {
                ans[i] = -1;
                continue;
            }
            for (int j = pos; j < n ; j++) {
                //if (y < nums[i]) break;

                if (nums[j] + x >= MAX) {
                    MAX = max(MAX, nums[j] ^ x);
                } else {
                    //cout << j << endl;
                    break;
                }
            }
            ans[i] = MAX;
        }

        return ans;
    }
};

字典树

810. 黑板异或游戏

数学题

如果数组长度为偶数,那么怎么拿,Alice都赢
但如果长度是奇数呢?奇数就输了吗?不一定,如果数组本来异或结果就为0,那么Alice还是赢

class Solution {
public:
    bool xorGame(vector<int>& nums) {
        if (nums.size() % 2 == 0) {
            return true;
        }
        int xorsum = 0;
        for (int num : nums) {
            xorsum ^= num;
        }
        return xorsum == 0;
    }
};

1035. 不相交的线

image-20210521083529102

lcs最长公共子序列

class Solution {
public:
    int maxUncrossedLines(vector<int>& nums1, vector<int>& nums2) {
        int n = nums1.size(), m = nums2.size();
        vector<vector<int>> dp(n + 1, vector<int>(m + 1));
        for (int i = 1; i <= n; ++i) {
            for (int j = 1; j <= m; ++j) {
                if (nums1[i - 1] == nums2[j - 1]) {
                    dp[i][j] = max(dp[i][j], dp[i - 1][j - 1] + 1);
                } else {
                    dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]);
                }
            }
        }
        return dp[n][m];
    }
};

692. 前K个高频单词

map考察自定义比较函数

class Solution {
public:
    typedef struct node {
        string s;
        int cnt;
    }node;
    static bool cmp(node a, node b) {
        if (a.cnt == b.cnt) {
            return a.s < b.s;
        }
        return a.cnt > b.cnt;
    }
    vector<string> topKFrequent(vector<string>& words, int k) {
        vector<string> ans(k);
        map<string, int> m_si;
        vector<node> vn;
        for (auto word : words) {
            m_si[word]++;
        }
        for (auto x : m_si) {
            vn.push_back({x.first, x.second});
        }

        sort(vn.begin(), vn.end(), cmp);
        for (int i = 0; i < k; i++) {
            ans[i] = vn[i].s;
        }
        return ans;
    }
};

1738. 找出第 K 大的异或坐标值

二维前缀和

class Solution {
public:
    int kthLargestValue(vector<vector<int>>& matrix, int k) {
        int m = matrix.size(), n = matrix[0].size();
        vector<vector<int>> pre(m + 1, vector<int>(n + 1));
        vector<int> results;
        for (int i = 1; i <= m; ++i) {
            for (int j = 1; j <= n; ++j) {
                pre[i][j] = pre[i - 1][j] ^ pre[i][j - 1] ^ pre[i - 1][j - 1] ^ matrix[i - 1][j - 1];
                results.push_back(pre[i][j]);
            }
        }

        sort(results.begin(), results.end(), greater<int>());
        return results[k - 1];
    }
};

1442. 形成两个异或相等数组的三元组数目

image-20210518221543980

n ^ 3做法:暴力枚举三个点,前缀和

class Solution {
public:
    int countTriplets(vector<int>& arr) {
        int n = arr.size();
        vector<int> sum(n, 0);
        sum[0] = arr[0];
        for (int i = 1; i < arr.size(); i++) {
            sum[i] = sum[i - 1] ^ arr[i];
        }
        int ans = 0;
        for (int i = 0; i < n; i++) {
            for (int j = i + 1; j < n; j++) {
                for (int k = j; k < n; k++) {
                    int a = sum[i] ^ sum[j - 1] ^ arr[i];
                    int b = sum[k] ^ sum[j] ^ arr[j];
                    if (a == b) {
                        //cout << i << j << k << endl;
                        ans++;
                    }
                }
            }
        }
        return ans;
    }
};

n^2做法:前缀和+问题转换

image-20210518222339213

class Solution {
public:
    int countTriplets(vector<int>& arr) {
        // 类似前缀和
        int n = arr.size();
        int* pre_ = new int[n + 1]();
        int sum = 0;
        pre_[0] = 0;
        for (int i = 1; i <= n; i++)
        {
            sum ^= arr[i - 1];
            pre_[i] = sum;         
        }
        int count = 0;
        for (int i = 0; i < n; i++)
        {
            for (int j = i + 1; j <= n; j++)
            {
                if (pre_[j] == pre_[i])
                {
                    count += j - i - 1;
                }
            }
        }
        return count;
    }
};

image-20210518222445318

993.二叉树的堂兄弟结点

BFS。

如果两个结点在每一层,那么在每一层for循环向队列添加元素的时候,就必然会在同一个for循环中被添加到队列中。

image-20210517074445863

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
 *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
 * };
 */
class Solution {
public:
    using PTT = pair<TreeNode*, TreeNode*>;
    bool isCousins(TreeNode* root, int x, int y) {
        // 使用队列q来进行bfs
        // 其中pair中,p.first 记录当前结点的指针,p.second 记录当前结点的父结点的指针
        queue<PTT> q;
        q.push({root, nullptr});
        while(!q.empty()) {
            int n = q.size();
            vector<TreeNode*> rec_parent; // 存放结点
            for(int i = 0; i < n; i++) {
                auto [cur, parent] = q.front(); q.pop();
                if(cur->val == x || cur->val == y)
                    rec_parent.push_back(parent);
                if(cur->left) q.push({cur->left, cur}); // 存放该当前结点子节点以及当前结点(父节点)
                if(cur->right) q.push({cur->right, cur});
            }
            // `x` 和 `y` 都没出现
            if(rec_parent.size() == 0)
                continue;
            // `x` 和 `y` 只出现一个
            else if(rec_parent.size() == 1)
                return false;
            // `x` 和 `y` 都出现了
            else if(rec_parent.size() == 2)
                // `x` 和 `y` 父节点 相同/不相同 ?
                return rec_parent[0] != rec_parent[1];
        }
        return false;
    }
};

421.数组中两个数最大异或值

  • Trie树
  • 暴力+剪枝
    • 如果i + j小于当前已知的ii ^ jj那么 i ^ j的值一定小于 ii ^ jj的值
    • 因为进位如果还小的话,明显不需要了
class Solution {
public:
    int findMaximumXOR(vector<int>& nums) {
        sort(nums.begin(), nums.end());
        int n = nums.size();
        long long maxValue = 0;
        for (int i = n -1; i >= 1; i--) {
            for (int j = i -1; j >= 0; j--) {
                if ((long long)(nums[i]) + nums[j] < maxValue) {
                    break;
                }
                maxValue = max(maxValue, (long long)(nums[i] ^ nums[j]));
            }
        }
        return maxValue;

    }
};

13. 罗马数字转整数

对于一个罗马数字字符串可以

  • 对于每一个字符 str[i]
  • 检查str[i - 1]str[i]组成的字符串是否在map映射中出现,如果出现则优先采用该双字符的,如果没有出现则采用单字符的
  • 因为是从后往前推,所有对于IX中的I会计算两次,即需要将IX9映射为8

image-20210515091540971

class Solution {
public:
    int romanToInt(string s) {
        unordered_map<string, int> m = {{"I", 1}, {"IV", 3}, {"IX", 8}, {"V", 5}, {"X", 10}, {"XL", 30}, {"XC", 80}, {"L", 50}, {"C", 100}, {"CD", 300}, {"CM", 800}, {"D", 500}, {"M", 1000}};
        int r = m[s.substr(0, 1)];
        for(int i=1; i<s.size(); ++i){
            string two = s.substr(i-1, 2);
            string one = s.substr(i, 1);
            r += m[two] ? m[two] : m[one];
        }
        return r;
    }
};

12. 罗马数字转整数

建立映射关系,然后按对应数字从大到小依次处理字符串和减去对应的值。

image-20210514090043335

class Solution {
public:
    string intToRoman(int num) {
        string strs[]= {"M", "CM", "D", "CD", "C", "XC", "L", "XL", "X", "IX", "V", "IV", "I"};
        int nums[] = {1000, 900, 500, 400, 100, 90, 50, 40, 10, 9, 5, 4, 1};
        string ans;
        for (int i = 0; num > 0 && i < 13; i++) {
            while (nums[i] <= num) {
                ans += strs[i];
                num -= nums[i];
            }
        }
        return ans;
    }
};

1310.子数组异或查询

前缀和

class Solution {
public:
    vector<int> xorQueries(vector<int>& arr, vector<vector<int>>& queries) {
        int n = arr.size();
        vector<int> sum(n);
        int len = queries.size();
        sum[0] = arr[0];
        for (int i = 1; i < n; i++) {
            sum[i] = sum[i - 1] ^ arr[i];
        }
        vector<int> ans(len);
        int cnt = 0;
        for (auto q : queries) {
            ans[cnt++] = sum[q[0]] ^ sum[q[1]] ^ arr[q[0]];
        }
        return ans;
    }
};

1734.解码异或后的排列

image-20210511151045068

数学思维

class Solution {
public:
    vector<int> decode(vector<int>& encoded) {
        int n = encoded.size() + 1;
        int total = 0;
        for (int i = 1; i <= n; i++) {
            total ^= i;
        }
        int odd = 0;
        // encoded[i] = perm[i] ^ perm[i + 1]
        // 步长为2, perm[0] ^ encoded[i] ^ encode[i + 2] ... = perm[0] ^ encoded[1] ... encoded[n - 1]
        for (int i = 1; i < n - 1; i += 2) {
            odd ^= encoded[i];
        }
        vector<int> perm(n);
        perm[0] = total ^ odd; // 所以perm[0]就可以求出来了
        for (int i = 0; i < n - 1; i++) {
            // 有了perm[0], encoded[i] ^ perm[i] = perm[i + 1]
            perm[i + 1] = perm[i] ^ encoded[i];
        }
        return perm;
    }
};

叶子相似的树

深度前序遍历记录叶子结点

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
 *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
 * };
 */
class Solution {
public:
    void preSearch(TreeNode* root1, vector<int> &seq) {
        if (root1 == nullptr) return;
        if (!root1 -> left & !root1 -> right) {
            seq.push_back(root1 -> val);
            return ;
        }
        
        preSearch(root1 -> left, seq);
        preSearch(root1 -> right, seq);
    }
    bool leafSimilar(TreeNode* root1, TreeNode* root2) {
        vector<int> seq1, seq2;
        preSearch(root1, seq1);
        preSearch(root2, seq2);
        return seq1 == seq2;
    }
};

1482. 制作m束花所需的最少天数

二分+贪心

image-20210509083648388

class Solution {
public:
    bool check(vector<int>& bloomDay, int m, int k, int limit) {
        int total = 0;
        int cur_cnt = 0;
        // 枚举在limit天 花盛开的情况
        for(auto day : bloomDay) {
            // 花开的天数 如果大于 当前天数limit说明还没有开放 将cur_cnt 重置 断开
            if(day > limit)
                cur_cnt = 0;
            else // 小于且连续 就++
                cur_cnt++;
            // 判断是否满足一束花
            if(cur_cnt >= k) {
                total++;
                cur_cnt = 0;
            }
        }
        return total >= m;
    }
    int minDays(vector<int>& bloomDay, int m, int k) {
        int n = bloomDay.size();
        if(m * k > n) return -1; // 不存在的情况是 花数 < 需要的花束
        // 优化 枚举范围在 min - max ele
        int left = *min_element(bloomDay.begin(), bloomDay.end());
        int right = *max_element(bloomDay.begin(), bloomDay.end());
        // 二分枚举天数 能缩小就缩小 不能缩小就扩大
        while(left <= right) {
            int mid = (left + right) / 2;
            if(check(bloomDay, m, k, mid))
                right = mid-1; // 缩小区间
            else
                left = mid+1; // 扩大区间
        }
        return right + 1;
    }
};

1723. 完成所有工作的最短时间

二分、回溯、剪枝

class Solution {
public:
    bool backtrack(vector<int>& jobs, vector<int>& workloads, int idx, int limit) {
        if (idx >= jobs.size()) {
            return true;
        }
        int cur = jobs[idx];
        for (auto& workload : workloads) {
            if (workload + cur <= limit) {
                workload += cur; // 选择当前的进行分配
                if (backtrack(jobs, workloads, idx + 1, limit)) {
                    return true;
                }
                workload -= cur; // 不选择当前的进行分配
            }
            // 如果当前工人未被分配工作,那么下一个工人也必然未被分配工作
            // 或者当前工作恰能使该工人的工作量达到了上限
            // 这两种情况下我们无需尝试继续分配工作
            if (workload == 0 || workload + cur == limit) { // 因为是整个for循环 每个都要尝试 这里剪枝
                break;
            }
        }
        return false;
    }

    bool check(vector<int>& jobs, int k, int limit) {
        vector<int> workloads(k, 0);
        return backtrack(jobs, workloads, 0, limit);
    }

    int minimumTimeRequired(vector<int>& jobs, int k) {
        sort(jobs.begin(), jobs.end(), greater<int>()); // 从大到小排列
        int l = jobs[0], r = accumulate(jobs.begin(), jobs.end(), 0); // limit -> [min, max]工作量
        while (l < r) { // 二分枚举limit
            int mid = (l + r) >> 1;
            if (check(jobs, k, mid)) {
                r = mid;
            } else {
                l = mid + 1;
            }
        }
        return l;
    }
};

1486. 数组异或操作

简答模拟

image-20210507085330656

class Solution {
public:
    int xorOperation(int n, int start) {
        int num = start;
        for (int i = 1; i < n; i++) {
            num ^= (start + 2 * i);
        }
        return num;
    }
};

1720. 解码异或后的数组

简单数论

image-20210506195533573

class Solution {
public:
    vector<int> decode(vector<int>& encoded, int first) {
        int n = encoded.size();
        vector<int> ans(n + 1, first);
        for (int i = 0; i < n; i++) {
            ans[i + 1] = ans[i] ^ encoded[i];
        }
        return ans;
    }
};

740. 删除并获得点数

image-20210505100653314

动态规划

class Solution {
public:
    int deleteAndEarn(vector<int>& nums) {
        if(nums.size() < 1) return 0;
        int maxn = 0;
        for(int it : nums)
            maxn = max(maxn, it);
        vector<int> cnt(maxn+1), dp(maxn+1);
        for(int it : nums)
            cnt[it]++;
        dp[1] = cnt[1];
        for(int i = 2; i <= maxn; i++)
            dp[i] = max(dp[i-1], dp[i-2] + cnt[i] * i);
        return dp[maxn];
    }
};

554. 砖墙

贪心 思维题

对于每一条垂线,穿过的砖块和穿过的间隙相加结果都是总高度 是个定值

所以只需要找出最多的间隙那个 就找到了高度减最多的间隙就找到了穿过的最少砖数。

image-20210502082716828

class Solution {
public:
    int leastBricks(vector<vector<int>>& wall) {
        unordered_map<int, int> cnt;
        for (auto& widths : wall) {
            int n = widths.size();
            int sum = 0;
            for (int i = 0; i < n - 1; i++) {
                sum += widths[i];
                cnt[sum]++;
            }
        }
        int maxCnt = 0;
        for (auto& [_, c] : cnt) {
            maxCnt = max(maxCnt, c);
        }
        return wall.size() - maxCnt;
    }
};

690. 员工的重要性

image-20210502083118041

bfs

/*
// Definition for Employee.
class Employee {
public:
    int id;
    int importance;
    vector<int> subordinates;
};
*/

class Solution {
public:
    int getImportance(vector<Employee*> employees, int id) {
        int ans = 0;
        //if (employees.size() == 0) return 0;
        unordered_set<int> emp_record;
        unordered_map<int, int> m;

        for (int i = 0; i < employees.size(); i++) {
            m[employees[i] -> id] = i;
        }

        queue<Employee> que_emp;
        que_emp.push(*employees[m[id]]);

        while (que_emp.size()) {
            Employee temp = que_emp.front();
            que_emp.pop();
            ans += temp.importance;
            for (auto num : temp.subordinates) {
                if (emp_record.count(num) == 0) {
                    emp_record.insert(num);
                    que_emp.push(*employees[m[num]]);
                }
            }
            
        }
        return ans;
    }
};

403. 青蛙过河

解法1:

DFS + 记忆化(记忆化搜索)

image-20210502083209208

class Solution {
public:
    using PII = pair<int, int>;
    unordered_map<int, unordered_set<int>> visited;
    unordered_set<int> stone_pos;
    int done;
    bool dfs(int prv_pos, int speed) {
        int cur_pos = prv_pos + speed;

        if(speed < 0 || !stone_pos.count(cur_pos)) // 不能向后跳 并且当前位置有石头存在 
            return false;
        if(visited[prv_pos].count(speed)) // 防止重复计算 以同一个速度到达同一个位置 代表是同一个状态
            return false;
        visited[prv_pos].insert(speed);//将当前新状态插入到数组中

        if(cur_pos == done) // 达到最后一个
            return true;
        
        // 进行递归搜索
        return dfs(cur_pos, speed-1) || 
            dfs(cur_pos, speed) || dfs(cur_pos, speed+1);   
    }
    bool canCross(vector<int>& stones) {
        int n = stones.size();
        // 保存石头位置
        stone_pos = unordered_set<int>(stones.begin(), stones.end());
        // 目标为最后一个位置
        done = stones.back();
		// 从0开始向跳一个
        return dfs(0, 1);
    }
};

动态规划

  • d[i][speed]表示以speed能否跳到第i个石头
  • 初始化:d[0][0] = 1
  • dp[i][speed] = dp[j][speed - 1] or dp[j][spped] or dp[j][speed + 1]
class Solution {
public:
    bool canCross(vector<int>& stones) {
        int n = stones.size();
        // dp[i][speed]:表示能否以speed的速度,到达第i个石头
        vector<vector<int>> dp(n, vector<int>(n, 0));
        dp[0][0] = 1;
        for(int i = 1; i < n; i++) {
            for(int j = 0; j < i; j++) {
                int speed = stones[i] - stones[j]; // 需要speed的距离才能从j到i
                if(speed <= 0 || speed > j+1) // 从j开始跳的距离最大为 j + 1, 距离超过 j + 1,说明不能一次跳跃到达
                    continue;
                    
                dp[i][speed] = dp[j][speed-1] || 
                    dp[j][speed] || dp[j][speed+1];
            }
        }
        for (int i = 0; i < n; i++) {
            if (dp[n - 1][i]) return true;
        }
        return false;
    }
};

633. 平方数之和

暴力

class Solution {
public:
    bool judgeSquareSum(int c) {
        for (long a = 0; a * a <= c; a++) {
            double b = sqrt(c - a * a);
            if (b == (int)b) {
                return true;
            }
        }
        return false;
    }
};

Go版本

func judgeSquareSum(c int) bool {
    for a := 0; a*a <= c; a++ {
        rt := math.Sqrt(float64(c - a*a))
        if rt == math.Floor(rt) {
            return true
        }
    }
    return false
}

双指针

  • left = 0, right = sqrt(c)

  • if (乘积大于目标 ) right --

  • if (成绩小于) left++

  • 终结条件(left > right), right == left 可能是8 = 2^2 + 2^2

func judgeSquareSum(c int) bool {
    left, right := 0, int(math.Sqrt(float64(c)))
    for left <= right {
        sum := left*left + right*right
        if sum == c {
            return true
        } else if sum > c {
            right--
        } else {
            left++
        }
    }
    return false
}

938.二叉搜索树的范围和

中序遍历

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
 *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
 * };
 */
class Solution {
public:
    void midSearch(TreeNode *root, vector<TreeNode *> &v) {
        if (!root)  return ;
        midSearch(root -> left, v);
        v.push_back(root);
        midSearch(root -> right, v);
    }
    int rangeSumBST(TreeNode* root, int low, int high) {
        vector<TreeNode *> v;
        int ans = 0;
        midSearch(root, v);
        int i = 0;
        for (i = 0; i < v.size(); i++) {
            if (v[i] -> val >= low) break;
        }
        for (int j = i; j < v.size(); j++) {
            if (v[j]->val <= high) ans += v[j]->val;
            else break;
        }
        return ans;
    }
};

官方题解

class Solution {
public:
    int rangeSumBST(TreeNode *root, int low, int high) {
        if (root == nullptr) {
            return 0;
        }
        // 当前值不在 [low, high]范围内
        if (root->val > high) { 
            return rangeSumBST(root->left, low, high);
        }
        if (root->val < low) {
            return rangeSumBST(root->right, low, high);
        }
        // 当前值 + 左孩子的值 + 右孩子的值
        return root->val + rangeSumBST(root->left, low, high) + rangeSumBST(root->right, low, high);
    }
};

1011. 在D天内送达包裹的能力

二分查找

二分枚举船的limit 然后检查该limit是否满足

class Solution {
public:
    bool check(vector<int> &weights, int D, int limit) {
        int cnt = 1, cur = 0;
        for(auto &weight : weights) {
            if(limit < weight) return false;
            if(cur + weight > limit) {
                cnt++;
                cur = 0;
            }
            cur += weight;
        }
        return cnt <= D; // <= D说明可以在D天之内运输完毕
    }
    int shipWithinDays(vector<int>& weights, int D) {
        int left = 1, right = 500*50000; // limit [1, 500 * 50000]
        int ans = right; // ans保存limit
        while(left <= right) {
            int mid = (left + right) / 2;
            if(check(weights, D, mid)) { // 可以满足 试图减少limit 因为要求最小值
                right = mid - 1;
                ans = mid;
            }
            else // 不能满足 增加limit
                left = mid + 1;
        }
        return ans;
    }
};


897. 递增搜索树

中序遍历存树的结点指针 然后重建树

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
 *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
 * };
 */
class Solution {
public:
    void midSearch(TreeNode *root, vector<TreeNode *> &nodes) {
        if (root == nullptr) return;
        midSearch(root -> left, nodes);
        nodes.push_back(root);
        midSearch(root -> right, nodes);
    }
    TreeNode* increasingBST(TreeNode* root) {
        vector<TreeNode *> nodes;
        if (root == nullptr) return nullptr;
        midSearch(root, nodes);
        nodes.push_back(nullptr);
        TreeNode *ans = nodes[0], *temp = nodes[0];
        for (int i = 0; i < nodes.size() - 1; i++) {
            nodes[i] -> left = nullptr;
            nodes[i] -> right = nodes[i + 1];
            
        }
        return ans;
    }
};

377.组合总数IV

DP

dp[i][j]表示长度为i的 构成总和为j的方案数

dp[0][0] = 1

image-20210424135158154

using ULL =  unsigned long long;
class Solution {
public:
    int combinationSum4(vector<int>& nums, int target) {
        int len = target;
        vector<vector<ULL>> f(len + 1,vector<ULL>(target + 1,0));
        f[0][0] = 1; // 数组长度为i 构成长度为j
        int ans = 0;
        for(int i = 1; i <= len; i++){
            for(int j = 0; j <= target; j++){
                for(auto x : nums){
                    if(j >= x) f[i][j] += f[i - 1][j - x];
                    // 从 i - 1状态转移过来 可能是数组任意一个数转移过来 
                }
            }
            // 将每一个长度 1 - target(全1) 的长度的方案书相加
            ans += f[i][target];
        }
        return ans;
    }
};
class Solution {
public:
    int combinationSum4(vector<int>& nums, int target) {
        vector<int> f(target + 1,0); // or vector<unsigned long long> f(target + 1,0); 就不用做取模的操作了
        f[0] = 1;
        for(int i = 1; i <= target; i++){
            for(auto x : nums){
                //c++计算的中间结果会溢出,但因为最终结果是int
                //因此每次计算完都对INT_MAX取模,0LL是将计算结果提升到long long类型
                if(i >= x) f[i] =(0LL + f[i] + f[i - x]) % INT_MAX;
            }
        }
        return f[target];
    }
};

368. 最大整除子集

DP

dp[i] 表示以nums[i]为末尾元素的整除子集的个数

初始状态:dp[i] = 1

第一遍找到每个以nums[i]结尾的整除子集大小。并记录最大值和最大子集长度dp[i] =max(dp[i], dp[j] + 1);

第二步:从后往前遍历先找到满足最大值和最大子集长度的元素,然后每选一个元素就减去一个最大子集长度,如maxSize = 5,一直递减直到1为止。判断元素是否符合的条件就是当前dp[i] == maxSize, 且maxVlaue % nums[i] == 0

class Solution {
public:
    vector<int> largestDivisibleSubset(vector<int>& nums) {
        int len = nums.size();
        sort(nums.begin(), nums.end());

        // 第 1 步:动态规划找出最大子集的个数、最大子集中的最大整数
        vector<int> dp(len, 1);
        int maxSize = 1;
        int maxVal = dp[0];
        for (int i = 1; i < len; i++) {
            for (int j = 0; j < i; j++) {
                // 题目中说「没有重复元素」很重要
                if (nums[i] % nums[j] == 0) {
                    dp[i] = max(dp[i], dp[j] + 1);
                }
            }

            if (dp[i] > maxSize) {
                maxSize = dp[i];
                maxVal = nums[i];
            }
        }

        // 第 2 步:倒推获得最大子集
        vector<int> res;
        if (maxSize == 1) {
            res.push_back(nums[0]);
            return res;
        }

        
        for (int i = len - 1; i >= 0 && maxSize > 0; i--) {
            if (dp[i] == maxSize && maxVal % nums[i] == 0) {
                res.push_back(nums[i]);
                maxVal = nums[i];
                maxSize--;
            }
        }
        return res;
    }
};

27. 移除元素

原地删除无序数组的特定项

image-20210419084355052

class Solution {
public:
    int removeElement(vector<int>& nums, int val) {
        int cur = 0;
        for (auto num : nums ){
            if (val != num) {
                nums[cur++] = num;
            }
        }
        return cur;
    }
};

26. 删除有序数组的重复项

原地删除有序数组的重复项

class Solution {
    public int removeDuplicates(int[] nums) {
    if (nums.length == 0) return 0;
    int i = 0;
    for (int j = 1; j < nums.length; j++) {
        if (nums[j] != nums[i]) {
            i++;
            nums[i] = nums[j];
        }
    }
    return i + 1;
    }

}

220. 存在重复元素

滑动窗口 + set维护窗口内的状态

image-20210417094515733

class Solution {
public:
    bool containsNearbyAlmostDuplicate(vector<int>& nums, int k, int t) {
        set<long> s;
        for (int i = 0; i < nums.size(); ++i) {
            // 指针定位比 long(nums[i])-t 大的数的位置
            auto pos = s.lower_bound(long(nums[i]) - t);
            // 如果存在且该数字也比 long(nums[i]) + t 小,说明存在我们想要的结果
            if (pos!=s.end() && *pos <= long(nums[i]) + t) return true;
            s.insert(nums[i]);
            if (s.size() > k) s.erase(nums[i-k]); // 维护滑动窗口
        }
        return false;
    }
};

213. 打家劫舍

参考198 做两次打家劫舍 分为:

  1. 打劫第一家
  2. 不打劫第一家

image-20210417094551127

class Solution {
public:
    int rob0(vector<int>& nums, int st, int end) {
        int n = nums.size();
        if (n == 0) return 0;
        //vector<int> dp(n + 1, 0); // dp[i]表示偷前i个房子的最大金额
        //dp[1] = nums[0];
        int cur = nums[st], pre = 0;
        for (int i = st + 2; i <= end; i++) {
            int temp = cur;
            cur = max(cur, pre + nums[i - 1]);
            pre = temp;
            //dp[i] = max(dp[i - 1], dp[i - 2] + nums[i - 1]);
        }
        return cur;
    }
    int rob(vector<int>& nums) {
        int n = nums.size();
        if (n == 1) return nums[0];
        //return rob0(nums, 0, n);
        return max(rob0(nums, 0, n - 1), rob0(nums, 1, n));
    }
};

208.Trie树

class Trie {
private:
    bool isEnd;
    Trie* next[26]; // 每一个结点都可能有26个字母的分支
public:
    Trie() {
        isEnd = false;
        memset(next, 0, sizeof(next));
    }
    
    void insert(string word) {
        Trie* node = this;
        // 存在就一直找到底 不存在就创建新的结点
        for (char c : word) {
            if (node->next[c-'a'] == NULL) {
                node->next[c-'a'] = new Trie();
            }
            node = node->next[c-'a'];
        }
        node->isEnd = true; // 标记为存在
    }
    
    bool search(string word) {
        Trie* node = this;
        for (char c : word) {
            node = node->next[c - 'a'];
            if (node == NULL) { // 当前字字母还没有被建立过 肯定不存在
                return false;
            }
        }
        // 在Trie树中找到了对应的字符串 并且已经移动了该字符串的最后一个位置
        return node->isEnd;
    }
    
    // 只要树中能找到对应的prefix的字符串就可 不管是否结尾
    bool startsWith(string prefix) {
        Trie* node = this;
        for (char c : prefix) {
            node = node->next[c-'a'];
            if (node == NULL) {
                return false;
            }
        }
        return true;
    }
};

783.二叉树节点的最小距离

image-20210413190558084

二叉树中序遍历得到有序数组 最小的距离为相邻的两个值的差值最小

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
 *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
 * };
 */
class Solution {
public:
    vector<int> nums;
    void dfs(TreeNode*root) {
        if (root == nullptr) return ;
        dfs(root -> left);
        nums.push_back(root -> val);
        dfs(root -> right);
    }
    int minDiffInBST(TreeNode* root) {
        dfs(root);
        int len = nums.size();
        int ans = INT_MAX;
        for (int i = 1; i < len; i++) {
            ans = min(ans, nums[i] - nums[i - 1]);
        }
        return ans;
    }
};

264. 丑数II

小顶堆: 堆顶为x , 下一个点就是 2x, 3x, 5x

image-20210411084001360

class Solution {
public:
    int nthUglyNumber(int n) {
        vector<int> factors = {2, 3, 5};
        unordered_set<long> seen;
        priority_queue<long, vector<long>, greater<long>> heap;
        seen.insert(1L);
        heap.push(1L);
        int ugly = 0;
        for (int i = 0; i < n; i++) {
            long curr = heap.top();
            heap.pop();
            ugly = (int)curr;
            for (int factor : factors) {
                long next = curr * factor;
                if (!seen.count(next)) {
                    seen.insert(next);
                    heap.push(next);
                }
            }
        }
        return ugly;
    }
};

263. 丑数

image-20210410142416824

class Solution {
public:
    bool isUgly(int n) {
        if (n <= 0) {
            return false;
        }
        vector<int> factors = {2, 3, 5};
        for (int factor : factors) {
            while (n % factor == 0) {
                n /= factor;
            }
        }
        return n == 1;
    }
};

33. 搜索旋转排序数组

二分搜索

class Solution {
public:
    int search(vector<int>& nums, int target) {
        int lo = 0, hi = nums.size() - 1;
        while (lo < hi) {
            int mid = (lo + hi) / 2;
            if ((nums[0] > target) ^ (nums[0] > nums[mid]) ^ (target > nums[mid]))
                lo = mid + 1;
            else
                hi = mid;
        }
        return lo == hi && nums[lo] == target ? lo : -1;
    }
};

二分模板

class Solution {
public:
    int search(vector<int>& nums, int target) {
        int lo = 0, hi = nums.size() - 1;
        while (lo < hi) {
            int mid = (lo + hi) / 2;
            if (check[nums[mid]])
                lo = mid + 1; // target -> [mid + 1, r]
            else
                hi = mid;// target -> [lo, mid]
        }
        return l;//最后是l == r
    }
};

80. 删除有序数组中的重复项II

双指针(快慢指针),cur表示当前遍历到的指针,fill表示需要填的指针,最后数组的长度就是填入了多少个数。

image-20210406083010537

class Solution {
public:
    int removeDuplicates(vector<int>& nums) {
        int n = nums.size();
        int cnt = 1;
        int fill = 1;
        // 这里直接从1开始避免后面条件判断
        for (int cur = 1; cur < n; ++cur) {
            if (nums[cur] != nums[cur-1])  
                cnt = 1;
            else 
                ++cnt;
            
            if (cnt <= 2){
                nums[fill] = nums[cur];
                ++fill;
            }
        }
        return fill;
    }
};

88.合并两个有序数组

2021-04-05

image-20210405081919970

class Solution {
public:
    void merge(vector<int>& nums1, int m, vector<int>& nums2, int n) {
         int idx = m + n - 1;
         int i = m - 1, j = n - 1;
        while (i >= 0 || j >= 0) {
            if (i >= 0 && j >= 0) {
                if (nums1[i] > nums2[j]) {
                    nums1[idx--] = nums1[i];
                    i--;
                } else {
                    nums1[idx--] = nums2[j];
                    j--;
                }
            } else {
                if (i == -1) {
                    nums1[idx--] = nums2[j];
                    j--;
                }else {
                    nums1[idx--] = nums1[i];
                    i--;
                }
            }
            
        }
    }
};

image-20210405081948299

781. 森林里面的兔子

2021-04-04

image-20210404085712859

class Solution {
public:
    int numRabbits(vector<int> &answers) {
        unordered_map<int, int> count;
        for (int y : answers) {
            ++count[y];
        }
        int ans = 0;
        for (auto &[y, x] : count) {
            ans += (x + y) / (y + 1) * (y + 1);
        }
        return ans;
    }
};

两只相同颜色的兔子看到的其他同色兔子数必然是相同的。反之,若两只兔子看到的其他同色兔子数不同,那么这两只兔子颜色也不同。

因此,将 \textit{answers}answers 中值相同的元素分为一组,对于每一组,计算出兔子的最少数量,然后将所有组的计算结果累加,就是最终的答案。

例如,现在有 13 只兔子回答 5。假设其中有一只红色的兔子,那么森林中必然有 6 只红兔子。再假设其中还有一只蓝色的兔子,同样的道理森林中必然有 66 只蓝兔子。为了最小化可能的兔子数量,我们假设这 12 只兔子都在这 13 只兔子中。那么还有一只额外的兔子回答 5,这只兔子只能是其他的颜色,这一颜色的兔子也有 6 只。因此这种情况下最少会有 18 只兔子。

一般地,如果有 x 只兔子都回答y,则至少有 x / (y + 1) 种不同的颜色,且每种颜色有 y+1 只兔子,因此兔子数至少为

image-20210404090415325

我们可以用哈希表统计 answers 中各个元素的出现次数,对每个元素套用上述公式计算,并将计算结果累加,即为最终答案。

向上取整: (x + y) / (y + 1)


1143.最长上升公共子序列

2021-04-05

https://leetcode-cn.com/problems/longest-common-subsequence/

image-20210405082645234

class Solution {
public:
    int longestCommonSubsequence(string text1, string text2) {
        const int M = text1.size();
        const int N = text2.size();
        vector<vector<int>> dp(M + 1, vector<int>(N + 1, 0));
        for (int i = 1; i <= M; ++i) {
            for (int j = 1; j <= N; ++j) {
                if (text1[i - 1] == text2[j - 1]) {
                    dp[i][j] = dp[i - 1][j - 1] + 1;
                } else {
                    dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]);
                }
            }
        }
        return dp[M][N];
    }
};

image-20210405083518341

class Solution {
public:
    int longestCommonSubsequence(string s1, string s2) {
    	int n = s1.size(), m = s2.size();
    	s1 = " " + s1, s2 = " " + s2;
    	int f[n+1][m+1];
    	memset(f, 0, sizeof(f));

    	for(int i = 0; i <= n; i++) f[i][0] = 1;
    	for(int j = 0; j <= m; j++) f[0][j] = 1;

    	for(int i = 1; i <= n; i++) {
    		for(int j = 1; j <= m; j++) {
    			if(s1[i] == s2[j])
    				f[i][j] = f[i-1][j-1] + 1;
    			else
    				f[i][j] = max(f[i-1][j], f[i][j-1]);
    		}
    	}

    	return f[n][m] - 1;
    }
};

面试题17.21 直方图的水量

image-20210405154710081

算法思想:总体积减去柱子的体积就是雨水的体积

class Solution {
public:
    int trap(vector<int>& height) {
        int Sum = accumulate(height.begin(), height.end(), 0); // 得到柱子的体积
        int volume = 0; // 总体积和高度初始化
        int high = 1;
        int size = height.size();
        int left = 0; // 双指针初始化
        int right = size - 1;
        while (left <= right) {
            // 在左边找到第一个高度 >= height的
            while (left <= right && height[left] < high) {
                left++;
            }
            // 在右边找到第一个高度 >= height的
            while (left <= right && height[right] < high) {
                right--;
            }
            // 二者差值+ 1就是这一层的体积
            volume += right - left + 1; // 每一层的容量都加起来
            high++; // 高度加一
        }
        return volume - Sum; // 总体积减去柱子体积,即雨水总量
    }
};

74.搜索二维矩阵

二分搜索。

image-20210405190726509

class Solution {
public:
    bool searchMatrix(vector<vector<int>> matrix, int target) {
        auto row = upper_bound(matrix.begin(), matrix.end(), target, [](const int b, const vector<int> &a) {
            return b < a[0];
        });
        if (row == matrix.begin()) {
            return false;
        }
        --row;
        return binary_search(row->begin(), row->end(), target);
    }
};

lower_bound( begin,end,num):从数组的begin位置到end-1位置二分查找第一个大于或等于num的数字,找到返回该数字的地址,不存在则返回end。通过返回的地址减去起始地址begin,得到找到数字在数组中的下标。

upper_bound( begin,end,num):从数组的begin位置到end-1位置二分查找第一个大于num的数字,找到返回该数字的地址,不存在则返回end。通过返回的地址减去起始地址begin,得到找到数字在数组中的下标。

在从大到小的排序数组中,重载lower_bound()和upper_bound()

lower_bound( begin,end,num,greater() ):从数组的begin位置到end-1位置二分查找第一个小于或等于num的数字,找到返回该数字的地址,不存在则返回end。通过返回的地址减去起始地址begin,得到找到数字在数组中的下标。

upper_bound( begin,end,num,greater() ):从数组的begin位置到end-1位置二分查找第一个小于num的数字,找到返回该数字的地址,不存在则返回end。通过返回的地址减去起始地址begin,得到找到数字在数组中的下标。

#include<bits/stdc++.h>
using namespace std;
int main() {
  int a[] = {1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4};
 
  cout << (lower_bound(a, a + 12, 4) - a) << endl; //输出 9
  cout << (upper_bound(a, a + 12, 4) - a) << endl; //输出 12
  cout << (lower_bound(a, a + 12, 1) - a) << endl; //输出 0
  cout << (upper_bound(a, a + 12, 1) - a) << endl; //输出 3
  cout << (lower_bound(a, a + 12, 3) - a) << endl; //输出 6
  cout << (upper_bound(a, a + 12, 3) - a) << endl; //输出 9
  cout << (lower_bound(a, a + 12, 5) - a) << endl; //输出 12
  cout << (upper_bound(a, a + 12, 5) - a) << endl; //输出 12
  cout << (lower_bound(a, a + 12, 0) - a) << endl; //输出 0
  cout << (upper_bound(a, a + 12, 0) - a) << endl; //输出 0
 
  return 0;
}

转变为一维二分

class Solution {
public:
    bool searchMatrix(vector<vector<int>>& matrix, int target) {
        int m = matrix.size(), n = matrix[0].size();
        int low = 0, high = m * n - 1;
        while (low <= high) {
            int mid = (high - low) / 2 + low;
            int x = matrix[mid / n][mid % n];
            if (x < target) {
                low = mid + 1;
            } else if (x > target) {
                high = mid - 1;
            } else {
                return true;
            }
        }
        return false;
    }
};


1006. 笨阶乘

2021-04-01

image-20210406185129755

class Solution {
public:
    int clumsy(int N) {
        stack<int> stk;
        stk.push(N);
        N--;

        int index = 0; // 用于控制乘、除、加、减
        while (N > 0) {
            if (index % 4 == 0) {
                stk.top() *= N;
            } else if (index % 4 == 1) {
                stk.top() /= N;
            } else if (index % 4 == 2) {
                stk.push(N);
            } else {
                stk.push(-N);
            }
            index++;
            N--;
        }

        // 把栈中所有的数字依次弹出求和
        int sum = 0;
        while (!stk.empty()) {
            sum += stk.top();
            stk.pop();
        }
        return sum;
    }
};
// 还有这种用法
stk.top() *= N; 
// 小技巧 面对要减去的数可以变为+(-N)
stk.push(-N);
// 相同的还有 堆默认大顶堆 如果要构建小顶堆 就把-element插入push进堆之中

Acwing每日一题

3617. 子矩形计数

image-20210602194457714

思维题

将k分成两个数相乘的形式(假设为k = a * b)

在row[]中找出有多少个a个连续的1;
在col[]中找出有多少个b个连续的1;

将两者相乘再累加;

image-20210602193453372

#include <iostream>
#include <algorithm>
#include <cstring>

using namespace std;

typedef long long LL;

const int N = 40010;

int n, m, k;
bool row[N], col[N];

LL count(int x, bool arr[], int len)
{
    int res = 0, cnt = 0;
    for(int i = 1;i <= len;i ++)
        if(arr[i]) cnt ++;
        else
        {
            if(cnt >= x) res += cnt - x + 1;
            cnt = 0;
        }
    if(cnt >= x) res += cnt - x + 1;

    return res;
}

int main()
{
    cin >> n >> m >> k;
    for(int i = 1;i <= n;i ++) cin >> row[i];
    for(int i = 1;i <= m;i ++) cin >> col[i];

    LL res = 0;
    for(int i = 1;i <= k && i <= 40010;i ++)
        if(k % i == 0)
        {
            int a = i, b = k / i;
            LL cnta = count(a, row, n), cntb = count(b, col, m);
            res += cnta * cntb;
        }

    cout << res << endl;

    return 0;
}

3583.整数分组

dp

dp[i][j]表示前i个数分成j组的最大方案数

转移方程dp[i][j] = max(dp[i - 1][j], dp[k - 1][j - 1] + (i - k + 1))表示将第i个字符划分到第j组中 且第j组是从k开始的 是当前组中最小的

#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 5010;

int n, m;
int w[N];
int f[N][N];

int main()
{
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= n; i ++ ) scanf("%d", &w[i]);
    sort(w + 1, w + n + 1);

    for (int i = 1, k = 1; i <= n; i ++ )
    {
        while (w[i] - w[k] > 5) k ++ ;
        for (int j = 1; j <= m; j ++ )
            f[i][j] = max(f[i - 1][j], f[k - 1][j - 1] + (i - k + 1));
    }

    printf("%d\n", f[n][m]);
    return 0;
}

3580.整数配对

简单思维

1.先预处理 把成对的处理完毕

2.再对新数组做排序

3.最优解是低位与最近高位做配对

#include <bits/stdc++.h>

using namespace std;
const int N = 1e5;
int a[N];
int main() {
    int n;
    cin>> n;
    unordered_map<int, int> um;
    for (int i = 0; i < n; i++) {
        cin >> a[i];
        um[a[i]]++;
    }
    vector<int> temp;
    for (auto x : um) {
        if (x.second % 2) {
            temp.push_back(x.first);
        }
    }
    n = temp.size();
    sort(temp.begin(), temp.end());
    
    int ans = 0;
    for (int i = 1; i < n; i += 2) {
        ans += abs(temp[i - 1] - temp[i]);
    }
    cout << ans << endl;
    return 0;
}

3333.K-优字符串

思维题目

image-20210521202455496

#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 200010;

int n, k;
char str[N];

int main()
{
    int T;
    scanf("%d", &T);
    for (int C = 1; C <= T; C ++ )
    {
        printf("Case #%d: ", C);
        scanf("%d%d%s", &n, &k, str);
        int cnt = 0;
        for (int i = 0, j = n - 1; i < j; i ++, j -- )
            if (str[i] != str[j])
                cnt ++ ;
        printf("%d\n", abs(cnt - k));
    }

    return 0;
}

3483.2的慕次方

递归

#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

string dfs(int n)
{
    string res;
    for (int i = 14; i >= 0; i -- )
        if (n >> i & 1)
        {
            // 不是第一个的话需要相加
            if (res.size()) res += '+';
            // i为0
            if (!i) res += "2(0)";
            // i为1
            else if (i == 1) res += "2";
            // i >= 2
            else res += "2(" + dfs(i) + ")";
        }
    return res;
}

int main()
{
    int n;
    while (cin >> n)
        cout << dfs(n) << endl;
    return 0;
}

作者:yxc
链接:https://www.acwing.com/activity/content/code/content/1253289/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

3404.谁是你的潜在朋友

简单统计

image-20210518191828793

#include <bits/stdc++.h>

using namespace std;
int a[202];
int main() {
    int n, m;
    cin >> n >> m;
    unordered_map<int, int> his;
    int x;
    for (int i = 0; i < n; i++) {
        cin >> x;
        a[i] = x;
        his[x] ++;
    }
    
    for (int i = 0; i < n; i++) {
        if (his[a[i]] - 1) {
            cout << his[a[i]] - 1 << endl;
        } else {
            cout << "BeiJu" << endl;
        }
    }
    return 0;
}

3481.阶乘的和

image-20210516200856928

启发式剪枝,二进制枚举,爆搜

#include <iostream>
#include <cstring>
#include <algorithm>
#include <unordered_set>

using namespace std;

int f[10];
unordered_set<int> S;

int main()
{
    for (int i = 0; i < 10; i ++ )
    {
        f[i] = 1;
        for (int j = i; j; j -- )
            f[i] *= j;
    }

    // 总共有10个数字 所有就有2^10个状态
    for (int i = 1; i < 1 << 10; i ++ )
    {
        int s = 0;
        // 对当前i状态检查 每一位的值
        for (int j = 0; j < 10; j ++ )
            if (i >> j & 1)
                s += f[j];
       // 插入到集合中
        S.insert(s);
    }

    int n;
    while (cin >> n, n >= 0)
        if (S.count(n))
            puts("YES");
        else
            puts("NO");
    return 0;
}

#include <iostream>
#include <cstring>
#include <algorithm>
#include <unordered_set>

using namespace std;

int f[10];
unordered_set<int> S;

void init() {
    f[0] = f[1] = 1;
    for (int i = 2; i < 10; i++) {
        f[i] = f[i - 1] * i;
        //cout << f[i] << endl;
    }
}
bool flag;
void dfs(int cur, int sum, int &target) {
    if (sum == target) {
        flag = 1; 
        return ;
    }
    if (sum > target) {
        return ;
    }
    for (int i = cur + 1; i < 10; i++) {
        dfs(i, sum + f[i], target);
    }
}


int main()
{
    int target;
    init();
    while (cin >> target) {
        if (target < 0) return 0;
        if (target == 0) {
            cout << "NO" <<endl;
            continue;
        }
        for (int i = 0; i < 10; i++) {
            dfs(i,  f[i], target);
            if (flag) {
                break;
            }
        }
        if (flag) {
            flag = 0;
            cout << "YES"  << endl;
            continue;
        } 
        cout << "NO" << endl;
    }
    return 0;
}



3502.不同路径数

迷宫模板题目

image-20210513193607496

#include <bits/stdc++.h>
using namespace std;
const int N = 7;
int n, m, k;
int a[N][N];

int dir[][2] = {
    {-1, 0},
    {1, 0},
    {0, 1},
    {0 , -1}
};
int v[N][N];

bool check(int x, int y ) {
    if (x >= 0 && y >= 0 && x < n && y < m) {
        return true;
    }
    return false;
}
set<string> strs;
void getNum(int i, int j, string s, int cnt) {
    if (k + 1 == cnt) {
        strs.insert(s);
        return;
    }
    
    for (int x = 0; x < 4; x++) {
        int dx = i + dir[x][0], dy = j + dir[x][1];
        if (check(dx, dy) && !v[dx][dy]) {
            //v[dx][dy] = 1;
            getNum(dx, dy, s + to_string(a[dx][dy]), cnt + 1);
            //v[dx][dy] = 0;
        }
    }
}

int main() {
    cin >> n >> m >> k;
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < m; j++) {
            cin >> a[i][j];
        }
    }

    for (int i = 0; i < n; i++) {
        for (int j = 0; j < m; j++) {
            //string temp = "";
            //v[i][j] = 1;
            getNum(i, j, to_string(a[i][j]), 1);
            //v[i][j] = 0;
        }
    }
    // for (auto x : strs) {
    //     cout << x << endl;
    // }
    cout << strs.size() << endl;
    return 0;
}

3493.最大的和

思维 + 滑动窗口 + 前缀和

可以选择的数是确定的。因为数>1,所有可以枚举每一个k区间长度内得不可直接选择的总和。然后相加。

#include <bits/stdc++.h>
using namespace std;

const int N = 1e5+5;
int a[N];
bool v[N];
long long sum[N];
int main() {
    int n, k;
    cin >> n >> k;
    
    for (int i = 0; i < n; i++) {
        cin >> a[i];
    }
    long long ans = 0;

    for (int i = 0; i < n; i++) {
        cin >> v[i];
        if (v[i]) {
            ans += a[i];
        }
    }
    sum[0] = !v[0]?0:a[0];
    for (int i = 1; i< n; i++) {
        sum[i] = sum[i - 1] + (!v[i]) * a[i];
    }

    long long temp = 0;
    
    for (int j = 0; j <= n - k; j++) {
        long long xx = sum[j + k - 1] - sum[j] + (!v[j]) * a[j];
        temp = max(temp, xx);
    }
    
    cout << temp + ans << endl;
    return 0;
}

3485.最大异或和(MT笔试)

image-20210510220806981

分析:

对于20%的数据显然可以使用n^3的算法。枚举起始下标和结束下标。然后再计算该区间的值。

对于50%的数据可以采用n^2的算法,使用前缀和快速求取。

对于100%的数据,可以先求出异或前缀和数组,然后问题就等价于从一堆数中选出两个数,他们两个的异或和最大。

即:si = a1^a2^..^ai , sj = a1^a2^..^aj, si ^ aj = a^i+1...aj

#include <bits/stdc++.h>
using namespace std;
const int N = 100010 * 31, M = 100010; // 元素数100010,trie树高31

int n, m; // n元素个数 m区间最大值

int s[M]; // s用于记录每次读取的值

int son[N][2], cnt[N], idx; // son是trim数

void insert(int x, int v) { // 将一个数字加入到trie树中
    int p = 0; // 初始在头结点
    for (int i = 30; i >= 0; i--) { // 一次判断31位数字
        int u = x >> i & 1;
        if (!son[p][u]) son[p][u] = ++idx; //如果还没有创建就创建该结点
        p = son[p][u]; // 移动到左子树或者右子树
        cnt[p] += v;// v = 1 新增加 , v = -1删除
    }
}

int query(int x) {
    int res = 0, p = 0;
    for (int i = 30; i >= 0; i--) {
        int u = x >> i & 1;
        // 每次选择不一样的子树 因为是异或,两者不同必然异或为1
        if (cnt[son[p][!u]]) p =son[p][!u] , res = res * 2 + 1; 
        
        else p = son[p][u] , res = res * 2;
    }
    return res;
}


int main() {

    cin >> n >> m;
    
    for (int i = 1; i<= n; i++) {
        int x;
        cin >> x;
        s[i] = s[i - 1] ^ x;
    }
    int res = 0;
    
    insert(s[0], 1);
    
    for (int i = 1; i <= n; i++) {
        if (i > m) insert(s[i - m - 1], -1); // 区间大于m就删除
        res = max(res, query(s[i]));
        insert(s[i], 1);
    }
    cout << res << endl;
    
    return 0;
}

3489.星期几--模拟

#include <iostream>
#include <cstring>
#include <algorithm>
#include <unordered_map>

using namespace std;

// 1-12月 正常日期
int months[13] = {
    0, 31, 28, 31, 30, 31, 30, 31, 31,
    30, 31, 30, 31
};

//建立map映射
unordered_map<string, int> month_name = {
    {"January", 1},
    {"February", 2},
    {"March", 3},
    {"April", 4},
    {"May", 5},
    {"June", 6},
    {"July", 7},
    {"August", 8},
    {"September", 9},
    {"October", 10},
    {"November", 11},
    {"December", 12},
};

// 星期几名字映射
string week_name[7] = {
    "Monday", "Tuesday", "Wednesday",
    "Thursday", "Friday", "Saturday",
    "Sunday"
};

// 判断是否是leap年
int is_leap(int year)
{
    return year % 4 == 0 && year % 100 || year % 400 == 0;
}

// 获取该年中某年某月的天数
int get_days(int year, int month)
{
    int s = months[month];
    if (month == 2) return s + is_leap(year);
    return s;
}

int main()
{
    int d, m, y;
    string str;
    while (cin >> d >> str >> y)
    {
        m = month_name[str];
        int i = 1, j = 1, k = 1; // i = year, j = month, k = days (1 - 30)
        int days = 0;
        while (i < y || j < m || k < d) // 目标日期还没有达到
        {
            k ++, days ++ ; // 本月天数++, 总天数++
            if (k > get_days(i, j)) // 如果本月天数 > 该月天数 即进入下一个月
            {
                k = 1; // 第一天
                j ++ ; // 月份++
                if (j > 12) // 如果月份大于 12 说明进入下一年
                {
                    j = 1; // 重新回到1月
                    i ++ ; // year++
                }
            }
        }
		//结果 = 总天数%7
        cout << week_name[days % 7] << endl;
    }

    return 0;
}

LC WeekContest

239场周赛

5746. 到目标元素的最小距离

image-20210503103928330

class Solution {
public:
    int getMinDistance(vector<int>& nums, int target, int start) {
        int ans = INT_MAX;
        for (int i = 0; i < nums.size(); i++) {
            if (nums[i] == target) {
                ans = min(ans, abs(i - start));
            }
        }
        return ans;
    }
};

5747. 将字符串拆分为递减的连续值

image-20210503104038591

二进制枚举

class Solution {
public:
    bool splitString(string s) {
        int n = s.size();
        for (int i = 1; i < 1 << (n - 1); i++) {
            bool flag = true;
            unsigned long long last = -1, x = s[0] - '0';
            for (int j = 0; j < n - 1; j++) {
                if (i >> j & 1) { // 当前位需要分割
                    // 检查合法性
                    if (last != -1 && x != last - 1) {
                        flag = false;
                        break;
                    }
                    // 记录上一个的数值
                    last = x;
                    // 新值为 j + 1
                    x = s[j + 1] - '0';
                } else { // 不分割 继续相加
                    x = x * 10 + s[j + 1] - '0';
                }
                
                
            }
            if (x != last - 1) flag = false;
            if (flag) return true;

        }
        return false;
    }
};

5749. 邻位交换的最小次数

image-20210503105657066

求当前全排列的下k个排列

next_permutation(b.begin(), b.end())

先找到第k个数,再逆序对交换。等价求解逆数对

C存A的元素在B中的下标位置和次数,

image-20210503110017347

class Solution {
public:
    int getMinSwaps(string a, int k) {
		string b = a;
        while (k--) next_permutation(b.begin(), b.end());
        
        int n = a.size();
        vector<int> c(n);
        int cnt[10] = {0};
        for (int i = 0; i < n; i++) {
            int x = a[i] - '0';
            cnt[x]++;
            int y = cnt[x];
            for (int j = 0; j < n; j++) {
                if (b[j] - '0' == x && --y == 0) {
                    c[i] = j;
                    break;
                }

            }

        }
        int res = 0;
        for (int i = 0; i < n; i++) {
            for (int j  = i + 1; j < n; j++) {
                if (c[i] > c[j]) 
                    res++;
            }
        }
        return res;
    }
};

236场周赛

数组元素积的符号

签到

image-20210411142048820

5727找出游戏的获胜者

约瑟夫环

循环链表模拟

递推

image-20210411142135411

class Solution {
public:
    typedef struct node {
        int val;
        node * next;
    }node;
    
    int findTheWinner(int n, int k) {
        if (n == 1) return 1;
        if (k == 1) return n;
        node *head = new node();
        head -> val = 1, head -> next = new node();
        node *temp = head;
        temp = temp -> next;
        for (int i = 2;i <= n; i++) {
            temp -> val = i;
            if (i != n)
                temp ->  next = new node();
            else 
                temp -> next = head;
            temp = temp -> next;
        }
        int remain = n;
        // for (int i = 0; i <= n; i++) {
        //     cout << head -> val << endl;
        //     head = head -> next;
        // }
        node * cur = head;
        while (remain != 1) {
            for (int i = 1; i < k - 1; i++) {
                cur = cur -> next;  
            }
            //cout << cur -> val << endl;
            //cout << cur -> next -> val  << endl;
            cur -> next = cur -> next -> next;
            cur = cur -> next;
            remain--;
        }
        return cur -> val;
    }
};
class Solution {
    public:
    int f(int n, int k) {
        if (n == 1) return 0;
        return (f(n - 1, k) + k) %n;
    }
    int findTheWinner(int n, int k) {
        return f(n, k) + 1;
    }
}

最少侧跳数

image-20210411142221079

const int N = 500010, INF = 1e8;

int f[N][3];

class Solution {
public:
    int minSideJumps(vector<int>& b) {
        // 中间跑道 跳到其它跑道都需要1
        f[0][1] = 0, f[0][0] = f[0][2] = 1;

        int n = b.size() - 1;
        for (int i = 1; i <= n; i ++ ) // 第i层
            for (int j = 0; j < 3; j ++ ) { // 第i层的 三个点
                f[i][j] = INF; // 初始状态为INF
                if (b[i] == j + 1) continue;// 当前要求的该点位障碍物 跳过
                for (int k = 0; k < 3; k ++ ) { // 枚举i - 1层的所有状态
                    if (b[i] == k + 1) continue; // 不能由i - 1层通过到达
                    int cost = 0; // 在同一个水平线上 cost = 0
                    if (k != j) cost = 1; // 说明不在同一个水平线上
                    f[i][j] = min(f[i][j], f[i - 1][k] + cost);
                }
            }
        return min(f[n][0], min(f[n][1], f[n][2]));
    }
};

前200道题目

1 两数之和

class Solution {
public:
    vector<int> twoSum(vector<int>& nums, int target) {
        vector<int> ans(2);
        int len = nums.size();
        for (int i = 0; i < len; i++) {
            for (int j = i + 1; j < len; j++) {
                if (nums[i] + nums[j] == target) {
                    ans[0] = i, ans[1] = j;
                    return ans;
                }
            }
        }
        return nums;
    }
};

2. 两数相加

class Solution {
    public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
        ListNode head = new ListNode(l1.val + l2.val);
        ListNode cur = head;
        while(l1.next != null || l2.next != null){
            l1 = l1.next != null ? l1.next : new ListNode();
            l2 = l2.next != null ? l2.next : new ListNode();
            cur.next = new ListNode(l1.val + l2.val + cur.val / 10);
            cur.val %= 10;
            cur = cur.next;
        }
        if(cur.val >= 10){
            cur.next = new ListNode(cur.val / 10);
            cur.val %= 10;
        }
        return head;
    }
}

3. 无重复的最长子串

class Solution {
public:
    int lengthOfLongestSubstring(string s) {
        if(s.size() == 0) return 0;
        unordered_set<char> lookup;
        int maxStr = 0;
        int left = 0;
        for(int i = 0; i < s.size(); i++){

            // 查找到了s[i] 删除左边
            while (lookup.find(s[i]) != lookup.end()){
                lookup.erase(s[left]);
                left ++;
            }

            maxStr = max(maxStr,i-left+1);
            lookup.insert(s[i]);
    }
        return maxStr;
        
    }
};

4. 寻找两个正序数组中的中位数

class Solution {
public:
    double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
        int len1 = nums1.size(), len2 = nums2.size();
        int n = len1 + len2;
        for (auto num : nums2) {
            nums1.push_back(num);
        }
        sort(nums1.begin(), nums1.end());
        if (n % 2 == 1) return nums1[n / 2];
        else {
            int mid = n / 2 - 1;
            double a = nums1[mid], b = nums1[mid + 1];
            
            return (a + b) / 2;
        }
        
    }
};

5. 最长回文串

class Solution {
public:
    string longestPalindrome(string s) {
        int n = s.size();
        vector<vector<int>> dp(n, vector<int>(n));
        string ans;
        for (int l = 0; l < n; ++l) {
            for (int i = 0; i + l < n; ++i) {
                int j = i + l;
                if (l == 0) {
                    dp[i][j] = 1;
                } else if (l == 1) {
                    dp[i][j] = (s[i] == s[j]);
                } else {
                    dp[i][j] = (s[i] == s[j] && dp[i + 1][j - 1]);
                }
                if (dp[i][j] && l + 1 > ans.size()) {
                    ans = s.substr(i, l + 1);
                }
            }
        }
        return ans;
    }
};

6. Z字形变换

class Solution {
public:
    string convert(string s, int numRows) {

        if (numRows == 1) return s;

        vector<string> rows(min(numRows, int(s.size())));
        int curRow = 0;
        bool goingDown = false;

        for (char c : s) {
            rows[curRow] += c;
            if (curRow == 0 || curRow == numRows - 1) goingDown = !goingDown;
            curRow += goingDown ? 1 : -1;
        }

        string ret;
        for (string row : rows) ret += row;
        return ret;
    }
};


7. 整数翻转

class Solution {
public:
    int reverse(int x) {
        string MAX = to_string(0x7fffffff); 
        int MIN_ = 0x80000000;
        string MIN = to_string(MIN_);

        bool sign = 0; // 0 positive
        if (x == MIN_) return 0;
        if (x < 0) x = -x, sign = 1;
        string old = to_string(x);
        std::reverse(old.begin(), old.end()); // 321
        if (sign) { // 负数
            old = '-' + old;
            if (MIN.length() <= old.length()) {
                if (old > MIN) return 0;
            }

        } else {
            if (MAX.length() <= old.length()) {
                if (MAX < old) return 0;
            }
        }
       //cout << old << endl;
        return atoi(old.c_str());

    }
};

8. 字符串转换整数

class Solution {
public:
    int myAtoi(string s) {
        int sign = 1, tmp = 0, i = 0;

        while(s[i] == ' ')  ++i;    //1.忽略前导空格

        if(s[i] == '+' || s[i] == '-')    //2.确定正负号
            sign = (s[i++] == '-') ? -1 : 1;   //s[i]为+的话sign依旧为1,为-的话sign为-1

        while(s[i] >= '0' && s[i] <= '9')   //3.检查输入是否合法
        {
            if(tmp > INT_MAX / 10 || (tmp == INT_MAX / 10 && s[i] - '0' > 7))    //4.是否溢出
                return sign == 1 ? INT_MAX : INT_MIN;
            tmp = tmp * 10 + (s[i++] - '0');    //5.不加括号有溢出风险
        }
        return tmp * sign;
    }
};


9. 判断是否为回文字符串

class Solution {
public:
    bool isPalindrome(int x) {
        string posiX = to_string(x);
        string temp = posiX;
        reverse(posiX.begin(), posiX.end());
        return temp == posiX;
    }
};
class Solution {
public:
    bool isPalindrome(int x) {
        if (x < 0) return false;
        long temp = 0;
        int r = x;
        while (r != 0) {
            if (temp > x) return false;
            temp = temp * 10 + r % 10;
            r /= 10;
        }
        return temp == x;
    }
};

10.正则表达式匹配

11.盛水最多的容器

class Solution {
    public int maxArea(int[] height) {
        int i = 0, j = height.length - 1, res = 0;
        while(i < j){
            res = height[i] < height[j] ? 
                Math.max(res, (j - i) * height[i++]): 
                Math.max(res, (j - i) * height[j--]); 
        }
        return res;
    }
}

12. 整数转罗马数字

13 个数字 从大到小 依次减到不能减

image-20210411171146513

class Solution {
public:
    string intToRoman(int num) {
        string strs[]= {"M", "CM", "D", "CD", "C", "XC", "L", "XL", "X", "IX", "V", "IV", "I"};
        int nums[] = {1000, 900, 500, 400, 100, 90, 50, 40, 10, 9, 5, 4, 1};
        string ans;
        for (int i = 0; num > 0 && i < 13; i++) {
            while (nums[i] <= num) {
                ans += strs[i];
                num -= nums[i];
            }
        }
        return ans;
    }
};

13. 罗马数字转整数

建立映射函数 对于每一个字符 计算它和它之前的 对于I, IV 扫描到V会查看前一个 即IV得到3 因为前一个I已经计算过为1。结合编码格式,因为VIV是不存在的。

class Solution {
public:
    int romanToInt(string s) {
        unordered_map<string, int> m = {{"I", 1}, {"IV", 3}, {"IX", 8}, {"V", 5}, {"X", 10}, {"XL", 30}, {"XC", 80}, {"L", 50}, {"C", 100}, {"CD", 300}, {"CM", 800}, {"D", 500}, {"M", 1000}};
        int r = m[s.substr(0, 1)];
        for(int i=1; i<s.size(); ++i){
            string two = s.substr(i-1, 2);
            string one = s.substr(i, 1);
            r += m[two] ? m[two] : m[one];
        }
        return r;
    }
};

14. 最长公共前缀

维护cnt指针 指向公共下标

image-20210411183533416

class Solution {
public:
    string longestCommonPrefix(vector<string>& strs) {
        string ans = "";
        
        int len = strs.size();
        if (len == 0) return "";
        int cnt = 0;
        while(1) {
            for (int i = 0; i < len; i++) {
                if (strs[i].length() == 0) return "";
                if (cnt < strs[i].length() && cnt < strs[0].length() && strs[i][cnt] == strs[0][cnt]) {
                    continue;
                } else {
                    return ans;
                }
            }
            ans += strs[0][cnt++];
        }
        return ans;
    }
};

15. 三数之和

排序 + 三指针

class Solution {
public:
    vector<vector<int>> threeSum(vector<int>& nums) {
        vector<vector<int>> res;

        int n = nums.size();
        if (n < 3) return res;

        sort(nums.begin(), nums.end());

        for (int i = 0; i < n; i ++ ) {
            //1. 开始预处理
            if (nums[i] > 0) return res;                    //若第一个数大于0,后面怎么加都不会等于0了
            if (i > 0 && nums[i] == nums[i - 1]) continue;  //跳过重复数字
            // i是第一个数 l 三元组中第二小 r 最大的
            int l = i + 1, r = n - 1;
            while (l < r) {
                if (nums[i] + nums[l] + nums[r] == 0) {

                    res.push_back({nums[i], nums[l], nums[r]});         //加入一个正确方案
                    while (l < r && nums[l] == nums[l + 1]) l ++ ;      //跳过重复数字
                    while (l < r && nums[r] == nums[r - 1]) r -- ;
                    l ++ ;                                              //左指针前进
                    r -- ;                                              //右指针后退
                }
                else if (nums[i] + nums[l] + nums[r] > 0) {
                    r -- ;      //和大于0,要减少总和之值,即右指针后退 因为左指针不能再后退了
                }
                else {
                    l ++ ;      //和小于0,要增加总和之值,即左指针前进, 因为右指针不能再前进了
                }
            }
        }

        return res;
    }
};

17. 电话号码的字符组合

回溯法

image-20210413192426018

class Solution {
public:
    vector<string> letterCombinations(string digits) {
        vector<string> combinations;
        if (digits.empty()) {
            return combinations;
        }
        unordered_map<char, string> phoneMap{
            {'2', "abc"},
            {'3', "def"},
            {'4', "ghi"},
            {'5', "jkl"},
            {'6', "mno"},
            {'7', "pqrs"},
            {'8', "tuv"},
            {'9', "wxyz"}
        };
        string combination;
        backtrack(combinations,combination, phoneMap, digits, 0 );
        return combinations;
    }

    void backtrack(vector<string>& combinations, string& combination,const unordered_map<char, string>& phoneMap, const string& digits, int index) {
        if (index == digits.length()) { // 当前达到字符组合长度
            combinations.push_back(combination);
        } else {
            char digit = digits[index];
            const string& letters = phoneMap.at(digit); // 找到对应的letters 
            for (const char& letter: letters) {
                combination.push_back(letter);
                backtrack(combinations,combination,phoneMap, digits, index + 1);
                combination.pop_back();
            }
        }
    }
};

19. 删除链表的第N个结点

先遍历一遍得到整个链表的长度 然后从head走 len - n - 1次,到需要删除的结点的前一个结点,然后通过该结点删除目标结点,返回head

边界条件:当len - n == 0 即需要删除头结点 head = head -> next就可了

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };
 */
class Solution {
public:
    ListNode* removeNthFromEnd(ListNode* head, int n) {
        ListNode *temp = head;
        int len = 0;
        while (temp) {
            len++;
            temp = temp -> next;
        }
        temp = head;
        len = len - n;
        if (len == 0) {
            head = head -> next;
            return head;
        }
        for (int i =0; i < len - 1; i++) {
            temp = temp -> next;
        }
        temp -> next = temp -> next -> next;
        return head;
    }
};

20. 有效的括号

用栈来模拟 对于左括号压栈 右括号要检查栈状态

class Solution {
public:
    bool isValid(string s) {
        stack<char> stk;
        for (auto c: s) {
            if (c == ')' || c == '}' || c == ']') {
                if (stk.size() == 0) return false;
                char top = stk.top();
                if (top == ')' || top == '}' || top == ']') return false;
                switch (c) {
                    case ')':
                        if (top != '(') return false;
                        break;
                    case '}':
                        if (top != '{') return false;
                        break;
                    case ']':
                        if (top != '[') return false;
                        break;
                }
                stk.pop();
            } else {
                stk.push(c);
            }
        }
        return stk.empty();
    }
};

21.合并两个有序链表

递归 每次递归都意味着一次分割

image-20210414181026316

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };
 */
class Solution {
public:
    ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
        // 如果为空 意味着有一个到达了尽头 将这个剩下的接到前一个后面即可
        if (l1 == NULL) {
            return l2;
        }
        if (l2 == NULL) {
            return l1;
        }
        // l1 结点小于l2结点
        if (l1->val <= l2->val) {
            l1->next = mergeTwoLists(l1->next, l2);
            return l1;
        }
        // l1结点小于等于l2结点
        l2->next = mergeTwoLists(l1, l2->next);
        return l2;
    }
};

22. 括号生成

递归方法(类似深搜)

image-20210414181806624

class Solution {
    // 检查括号是否有效的一种方式
    bool valid(const string& str) {
        int balance = 0;
        for (char c : str) {
            if (c == '(') {
                ++balance;
            } else {
                --balance;
            }
            if (balance < 0) {
                return false;
            }
        }
        return balance == 0;
    }

    void generate_all(string& current, int n, vector<string>& result) {
        // n是左右括号的个数为2 * n
        if (n == current.size()) {
			// 判断此次迭代的是否符合规则
            if (valid(current)) {
                result.push_back(current);
            }
            return;
        }
        // 此处两种方案 : 当前填'(' or 当前填 ')'
        current += '(';
        generate_all(current, n, result);
        current.pop_back();
        
        current += ')';
        generate_all(current, n, result);
        current.pop_back();
    }
public:
    vector<string> generateParenthesis(int n) {
        vector<string> result;
        string current;
        generate_all(current, n * 2, result);
        return result;
    }
};

回溯方法

优化:跟踪判断当前括号序列是否有效 -- 根据左括号和右括号个数来判断

class Solution {
    void backtrack(vector<string>& ans, string& cur, int open, int close, int n) {
        if (cur.size() == n * 2) {
            ans.push_back(cur);
            return;
        }
        // 左括号小于n
        if (open < n) {
            cur.push_back('(');
            backtrack(ans, cur, open + 1, close, n);
            cur.pop_back();
        }
        // 右括号小于左括号
        if (close < open) {
            cur.push_back(')');
            backtrack(ans, cur, open, close + 1, n);
            cur.pop_back();
        }
    }
public:
    vector<string> generateParenthesis(int n) {
        vector<string> result;
        string current;
        backtrack(result, current, 0, 0, n);
        return result;
    }
};

23. 合并K个排序链表

K次合并 两个有序的链表

class Solution {
public:
    ListNode* mergeTwoLists(ListNode *a, ListNode *b) {
        if ((!a) || (!b)) return a ? a : b;
        ListNode head, *tail = &head, *aPtr = a, *bPtr = b;
        while (aPtr && bPtr) {
            if (aPtr->val < bPtr->val) {
                tail->next = aPtr; aPtr = aPtr->next;
            } else {
                tail->next = bPtr; bPtr = bPtr->next;
            }
            tail = tail->next;
        }
        tail->next = (aPtr ? aPtr : bPtr);
        return head.next;
    }

    ListNode* mergeKLists(vector<ListNode*>& lists) {
        ListNode *ans = nullptr;
        for (size_t i = 0; i < lists.size(); ++i) {
            ans = mergeTwoLists(ans, lists[i]);
        }
        return ans;
    }
};

24. 两两交换链表中的节点

递归

class Solution {
public:
    ListNode* swapPairs(ListNode* head) {
        if (head == nullptr || head->next == nullptr) {
            return head;
        }
        ListNode* newHead = head->next; // newHead表示第二个节点
        head->next = swapPairs(newHead->next);// 第一个节点指向下一个递归
        newHead->next = head;// 将第二个节点的next 指向第一个
        return newHead;
    }
};

26. 删除有序数组中的重复项

双指针 一次循环

i指向当前下标 j指向循环

class Solution {
    public int removeDuplicates(int[] nums) {
    if (nums.length == 0) return 0;
    int i = 0;
    for (int j = 1; j < nums.length; j++) {
        if (nums[j] != nums[i]) {
            i++;
            nums[i] = nums[j];
        }
    }
    return i + 1;
}

}

31. 下一个排列

一个很秒的思路

class Solution {
public:
    void nextPermutation(vector<int>& nums) {
        int i = nums.size() - 2, j = nums.size() - 1;
        while(i >= 0 && nums[i] >= nums[i+1])   --i;    //寻找比后面那个数小的nums[i]
        if(i >= 0)   
        {
            while(j >= 0 && nums[j] <= nums[i]) --j;    //寻找比nums[i]大的第一个数
            swap(nums[i], nums[j]);
        }
        sort(nums.begin() + i + 1, nums.end());     //如果不存在下一个排列,i为-1
    }
};

198. 打家劫舍

简单dp

dp[i]表示偷前i家能得到的最大值

dp[i] = max(dp[i - 1], dp[i - 2] + nums[i - 1])

class Solution {
public:
    int rob(vector<int>& nums) {
        int n = nums.size();
        if (n == 0) return 0;
        vector<int> dp(n + 1, 0); // dp[i]表示偷前i个房子的最大金额
        dp[1] = nums[0];
        for (int i = 2; i <= n; i++) {
            dp[i] = max(dp[i - 1], dp[i - 2] + nums[i - 1]);
        }
        return dp[n];
    }
};

进一步对空间进行压缩

class Solution {
public:
    int rob(vector<int>& nums) {
        int n = nums.size();
        if (n == 0) return 0;
        //vector<int> dp(n + 1, 0); // dp[i]表示偷前i个房子的最大金额
        //dp[1] = nums[0];
        int cur = nums[0], pre = 0;
        for (int i = 2; i <= n; i++) {
            int temp = cur;
            cur = max(cur, pre + nums[i - 1]);
            pre = temp;
            //dp[i] = max(dp[i - 1], dp[i - 2] + nums[i - 1]);
        }
        return cur;
    }
};

常见面试题

自建链表

image-20210518224004648

#include <iostream>
using namespace std;
struct node{
	int data;
	node *next;
	node(int data,node *next=NULL){
		this->data = data;
		this->next = next;
	}
};
 
node *createlist(const int num){
	node *head = NULL;
    for (int i = 0; i < num; i++) {
        head = new node(i, head);
    }
	return head;
}
 
void displaylist(node *head){
	cout<<"list node -> ";
	while(head!=NULL){
		cout<<head->data<<" ";
		head = head->next;
	}
	cout<<endl;
}
 
int main(){
	node *head = createlist(5);
	displaylist(head);
	return 0;
}

反转链表

class Solution {
public:
    ListNode* reverseList(ListNode* head) {
        ListNode* prev = nullptr;
        ListNode* curr = head;
        while (curr) {
            ListNode* next = curr->next;
            curr->next = prev;
            prev = curr;
            curr = next;
        }
        return prev;
    }
};

class Solution {
public:
    ListNode* reverseList(ListNode* head) {
        if (!head || !head->next) {
            return head;
        }
        ListNode* newHead = reverseList(head->next);
        head->next->next = head;
        head->next = nullptr;
        return newHead;
    }
};

环形链表

哈希表存放走过的记录

class Solution {
public:
    bool hasCycle(ListNode *head) {
        unordered_set<ListNode*> seen;
        while (head != nullptr) {
            if (seen.count(head)) {
                return true;
            }
            seen.insert(head);
            head = head->next;
        }
        return false;
    }
};

快慢指针

class Solution {
public:
    bool hasCycle(ListNode* head) {
        if (head == nullptr || head->next == nullptr) {
            return false;
        }
        ListNode* slow = head;
        ListNode* fast = head->next;
        while (slow != fast) {
            if (fast == nullptr || fast->next == nullptr) {
                return false;
            }
            slow = slow->next;
            fast = fast->next->next;
        }
        return true;
    }
};

合并两个有序链表

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };
 */
class Solution {
public:
    ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
        // 如果为空 意味着有一个到达了尽头 将这个剩下的接到前一个后面即可
        if (l1 == NULL) {
            return l2;
        }
        if (l2 == NULL) {
            return l1;
        }
        // l1 结点小于l2结点
        if (l1->val <= l2->val) {
            l1->next = mergeTwoLists(l1->next, l2);
            return l1;
        }
        // l1结点小于等于l2结点
        l2->next = mergeTwoLists(l1, l2->next);
        return l2;
    }
};
posted @ 2021-05-16 20:51  DengSchoo  阅读(86)  评论(0编辑  收藏  举报