力扣刷题【研一暑假呜】

滑动窗口

思路:
遍历右端点,如果不符合条件/符合条件,循环 调整左端点 直至

209.长度最小的子数组

给定一个含有 n 个正整数的数组和一个正整数 target 。
找出该数组中满足其总和大于等于 target 的长度最小的 子数组 [ n u m s l , n u m s l + 1 , . . . , n u m s r − 1 , n u m s r ] [nums_l, nums_{l+1}, ..., nums_{r-1}, nums_r] [numsl,numsl+1,...,numsr1,numsr] ,并返回其长度。如果不存在符合条件的子数组,返回 0 。

示例 1:
输入:target = 7, nums = [2,3,1,2,4,3]
输出:2
解释:子数组 [4,3] 是该条件下的长度最小的子数组。

遍历右端点,如果符合条件(大于等于 target ),左端点一直调整(调整过程中更新res因为要最短的)

class Solution {
public:
    int minSubArrayLen(int target, vector<int>& nums) {
        int l=0,r=0;
        int res = INT_MAX;
        int tmp = 0;
        while(r<nums.size()){
            tmp += nums[r];
            while(tmp >= target){
                res = min(res,r-l+1);
                tmp -= nums[l];
                l++;
            }
            r++;
        }
        if (res< INT_MAX)
            return res;
        else 
            return 0;
    }
};
3.给定一个字符串 s ,请你找出其中不含有重复字符的 最长 子串的长度。

示例 1:
输入: s = “abcabcbb”
输出: 3
解释: 因为无重复字符的最长子串是 “abc”,所以其长度为 3。

class Solution {
public:
    int lengthOfLongestSubstring(string s) {
        int len=s.size();
        int l=0,r=0;
        map<char,int> st;//char 次数
        if(len==0)
            return 0;
        int res=1;
        while(r<len){
            //l指针移到r 所在位置,因为最大的就是[l,r),这区间的不可能出现更大的了
            //所以将r之前的全删掉
            while(st[s[r]]) { //和上面一样都是while
                st[s[l]]--;l++;
            }
            res=max(res,r-l+1);        
            st[s[r]]+=1;
            r++;     
        }
        return res;
    }
};
713. 给你一个整数数组 nums 和一个整数 k ,请你返回子数组内所有元素的乘积严格小于 k 的连续子数组的数目。

示例 1:
输入:nums = [10,5,2,6], k = 100
输出:8
解释:8 个乘积小于 100 的子数组分别为:[10]、[5]、[2],、[6]、[10,5]、[5,2]、[2,6]、[5,2,6]。
需要注意的是 [10,5,2] 并不是乘积小于 100 的子数组。
示例 2:
输入:nums = [1,2,3], k = 0
输出:0

class Solution {
public:
    int numSubarrayProductLessThanK(vector<int>& nums, int k) {
        if (k==0 || k==1)
            return 0;
        int l=0,r=0;
        int len=nums.size();
        int tmp=1,res=0;
        while(r<len){
            tmp *= nums[r];//这两的顺序
            while(tmp>=k){//好吧,一直都是这样的,先把右端点加进去,然后while直接判断
                tmp /= nums[l];
                l++;
            }
            res += r-l+1;//如果[l,r]小于k,那么[l+1,r][l+2,r],[r]肯定也小于k
            r++;
        }
        return res;
    }
};

二叉树

千万不要一上来就陷入二叉树的细节

104. 二叉树的最大深度

给定一个二叉树 root ,返回其最大深度。
二叉树的 最大深度 是指从根节点到最远叶子节点的最长路径上的节点数。
时间复杂度: O ( n ) O(n) O(n),其中 nnn 为二叉树的节点个数。
空间复杂度: O ( n ) O(n) O(n)。最坏情况下,二叉树退化成一条链,递归需要 O ( n ) O(n) O(n) 的栈空间。

class Solution {
public:
    int maxDepth(TreeNode* root) {
        if(root==nullptr) return 0;
        int l=maxDepth(root->left);
        int r=maxDepth(root->right);
        return max(l,r)+1;
    }
};

方法2:用全局变量

class Solution {
    int ans = 0;

    void dfs(TreeNode *node, int cnt) {
        if (node == nullptr) return;
        ++cnt;
        ans = max(ans, cnt);
        dfs(node->left, cnt);
        dfs(node->right, cnt);
    }

public:
    int maxDepth(TreeNode *root) {
        dfs(root, 0);
        return ans;
    }
};
111. 二叉树的最小深度

给定一个二叉树,找出其最小深度。
最小深度是从根节点到最近叶子节点的最短路径上的节点数量。
说明:叶子节点是指没有子节点的节点。
在这里插入图片描述

class Solution {
    int ans = INT_MAX;
    void dfs(TreeNode* root,int cnt){
        if(root == nullptr) return;
        cnt++;
        if(root->left == root->right){
            ans=min(ans,cnt);return;
        } 
        dfs(root->left,cnt);
        dfs(root->right,cnt);
        return;
    }
public:
    int minDepth(TreeNode* root) {
        dfs(root,0);
        return root ? ans : 0;
    }
};

在这里插入图片描述

优化
如果递归中发现 cnt≥ans,由于继续向下递归也不会让 ans\textit{ans}ans 变小,直接返回。
这一技巧叫做「最优性剪枝」。

void dfs(TreeNode* root,int cnt){
        if(root == nullptr || cnt>=ans) return;
        cnt++;
        if(root->left == root->right){// node 是叶子
            ans=min(ans,cnt);return;
        } 
        dfs(root->left,cnt);
        dfs(root->right,cnt);
        return;
    }

时间复杂度: O ( n ) \mathcal{O}(n) O(n),其中 n为二叉树的节点个数。
空间复杂度: O ( n ) \mathcal{O}(n) O(n)。最坏情况下,二叉树退化成一条链,递归需要 O ( n ) \mathcal{O}(n) O(n)的栈空间。

112.路径总和

给你二叉树的根节点 root 和一个表示目标和的整数 targetSum 。判断该树中是否存在 根节点到叶子节点 的路径,这条路径上所有节点值相加等于目标和 targetSum 。如果存在,返回 true ;否则,返回 false 。

叶子节点 是指没有子节点的节点。

class Solution {
public:
    bool hasPathSum(TreeNode* root, int targetSum) {
        if(root==nullptr) return false;
        targetSum -= root->val;//
        if(root->left == root->right && targetSum==0) return true;
        return hasPathSum(root->left,targetSum) || hasPathSum(root->right,targetSum);//
    }
};
129. 求根节点到叶节点数字之和

给你一个二叉树的根节点 root ,树中每个节点都存放有一个 0 到 9 之间的数字。
每条从根节点到叶节点的路径都代表一个数字:

例如,从根节点到叶节点的路径 1 -> 2 -> 3 表示数字 123 。
计算从根节点到叶节点生成的 所有数字之和 。

class Solution {
    int ans=0;
    void dfs(TreeNode* root, int x){
        if(root==nullptr) return;
        x = x * 10 + root->val;
        if(root->left==root->right) {
            ans += x;return;
        }
        dfs(root->left,x);
        dfs(root->right,x);
    }
public:
    int sumNumbers(TreeNode* root) {
        dfs(root,0);
        return ans;
    }
};
int goodNodes(TreeNode* root,int value=INT_MIN) {//value表示当前遇到的最大值
        if(root==nullptr) return 0;
        int left = goodNodes(root->left,max(value,root->val));
        int right = goodNodes(root->right,max(value,root->val));
        return left+right+(value<=root->val);//如果当前结点的值 < 当前遇到的最大值,该结点就符合要求
    }
1448. 统计二叉树中好节点的数目

给你一棵根为 root 的二叉树,请你返回二叉树中好节点的数目。

「好节点」X 定义为:从根到该节点 X 所经过的节点中,没有任何节点的值大于 X 的值。
在这里插入图片描述
在深度优先遍历的过程中,记录从根节点到当前节点的路径上所有节点的最大值,若当前节点的值大于等于该最大值,则认为当前节点是好节点。

class Solution {
    int dfs(TreeNode* root,int preMax = INT_MIN){//要用参数传递更新 当前遇到的最大值的
        if(root==nullptr) return 0;
        int ans = 0;
        if(root->val >= preMax){
            preMax = root->val;
            ans++;
        }
        ans += dfs(root->left,preMax) + dfs(root->right,preMax);
        return ans;
    }
public:
    int goodNodes(TreeNode* root) {//value表示当前遇到的最大值
        return dfs(root);
    }
};

双指针

双向双指针 接雨水

11. 盛最多水的容器

给定一个长度为 n 的整数数组 height 。有 n 条垂线,第 i 条线的两个端点是 (i, 0) 和 (i, height[i]) 。
找出其中的两条线,使得它们与 x 轴共同构成的容器可以容纳最多的水。
返回容器可以储存的最大水量。
在这里插入图片描述
因为宽在缩小,所以 看那边高度小,就不要这个高度小的 「谁小移动谁」

第一次没有想到用双指针。。。
n = = h e i g h t . l e n g t h , 2 < = n < = 1 0 5 , O ( n 2 ) n == height.length,2 <= n <= 10^5, O(n^2) n==height.length2<=n<=105,O(n2)会超时

在每个状态下,无论长板或短板向中间收窄一格,都会导致水槽 底边宽度 −1-1−1​ 变短:>

  • 若向内 移动短板 ,水槽的短板 min(h[i],h[j])可能变大,因此下个水槽的面积 可能增大 。
  • 若向内 移动长板 ,水槽的短板 min(h[i],h[j])不变或变小,因此下个水槽的面积 一定变小 。
class Solution {
public:
    int maxArea(vector<int>& height) {
        int len=height.size();
        int left=0,right=len-1;
        int ans=0;
        while(left < right){
            int tmp = (right-left)*min(height[left],height[right]);
            ans = max(ans,tmp);
            if(height[left]<=height[right]) left++;
            else right--;
        }
        return ans;
    }
};
42.接雨水

给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。
在这里插入图片描述
求每一列能装的水,我们只需要关注当前列,以及其左边最高的墙,和其右边最高的墙。
根据较矮的那个墙和当前列的墙的高度可以分为三种情况。

  • 较矮的墙的高度大于当前列的墙的高度
  • 较矮的墙的高度小于(等于)当前列的墙的高度:正在求的列不会有水,因为它大于了两边较矮的墙。
分成一列一列 理解

第一次时间复杂度 O ( n 2 ) O(n^2) O(n2),超时了
后面想,对于一个序列,每个位置的左边最大值和 右边最大值有关系------->也就是下面的动态规划里的==每个位置的左边最大值max_left [i] = Max(max_left [i-1],height[i-1])。==它前边的墙的左边的最高高度** 和它前边的墙的高度选一个较大的,就是当前列左边最高的墙了
遍历所有列,每列能接的雨水=min(左边最大值,右边最大值)-当前高度
左边最大值dp[i]=max(dp[i-1],h[i-1]) h(i-1)是因为不包含当前这一列
右边最大值dp[i]=max(dp[i+1],h[i+1])从大到小遍历

在这里插入图片描述

public int trap(int[] height) {
    int sum = 0;
    int[] max_left = new int[height.length];
    int[] max_right = new int[height.length];
    
    for (int i = 1; i < height.length - 1; i++) {
        max_left[i] = Math.max(max_left[i - 1], height[i - 1]);
    }
    for (int i = height.length - 2; i >= 0; i--) {
        max_right[i] = Math.max(max_right[i + 1], height[i + 1]);
    }
    for (int i = 1; i < height.length - 1; i++) {
        int min = Math.min(max_left[i], max_right[i]);
        if (min > height[i]) {
            sum = sum + (min - height[i]);
        }
    }
    return sum;
}
双指针

max_left [ i ]max_right [ i ] 数组中的元素我们其实只用一次,然后就再也不会用到了。所以我们可以不用数组,只用一个元素就行了。但是会发现我们不能同时把 max_right 的数组去掉,因为最后的 for 循环是从左到右遍历的,而 max_right 的更新是从右向左的。

所以这里要从两个方向去遍历。
那么什么时候从左到右,什么时候从右到左呢?

max_left = Math.max(max_left, height[i - 1]);
max_right = Math.max(max_right, height[i + 1]);

只要保证 height [ left - 1 ] < height [ right + 1 ] ,那么 max_left 就一定小于 max_right

public int trap(int[] height) {
    int sum = 0;
    int max_left = 0;
    int max_right = 0;
    int left = 1;
    int right = height.length - 2; // 加右指针进去
    for (int i = 1; i < height.length - 1; i++) {
        //从左到右更
        //当前是left,计算其左1之前的最大值和左一哪个大
        if (height[left - 1] < height[right + 1]) {
            max_left = Math.max(max_left, height[left - 1]);
            int min = max_left;
            if (min > height[left]) {
                sum = sum + (min - height[left]);
            }
            left++;
        //从右到左更
        } else {
            max_right = Math.max(max_right, height[right + 1]);
            int min = max_right;
            if (min > height[right]) {
                sum = sum + (min - height[right]);
            }
            right--;
        }
    }
    return sum;
}

说到栈,我们肯定会想到括号匹配了。我们仔细观察蓝色的部分,可以和括号匹配类比下。每次匹配出一对括号(找到对应的一堵墙),就计算这两堵墙中的水。

  • 当前高度小于等于栈顶高度,入栈,指针后移。

  • 当前高度大于栈顶高度,出栈,计算出当前墙和栈顶的墙之间水的多少,然后计算当前的高度和新栈的高度的关系,重复第 2 步。直到当前墙的高度不大于栈顶高度或者栈空,然后把当前墙入栈,指针后移。

public int trap6(int[] height) {
    int sum = 0;
    Stack<Integer> stack = new Stack<>();
    int current = 0;
    while (current < height.length) {
        //如果栈不空并且当前指向的高度大于栈顶高度就一直循环
        while (!stack.empty() && height[current] > height[stack.peek()]) {
            int h = height[stack.peek()]; //取出要出栈的元素
            stack.pop(); //出栈
            if (stack.empty()) { // 栈空就出去
                break; 
            }
            int distance = current - stack.peek() - 1; //两堵墙之前的距离。
            int min = Math.min(height[stack.peek()], height[current]);
            sum = sum + distance * (min - h);
        }
        stack.push(current); //当前指向的墙入栈
        current++; //指针后移
    }
    return sum;
}

时间复杂度:虽然 while 循环里套了一个 while 循环,但是考虑到每个元素最多访问两次,入栈一次和出栈一次,所以时间复杂度是 O(n)。
空间复杂度:O(n)。栈的空间。

作者:windliang
链接:https://leetcode.cn/problems/trapping-rain-water/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

方法一:前后缀分解

class Solution {
public:
    int trap(vector<int>& height) {
        int len = height.size();
        vector<int> pre_m(len);
        vector<int> after_m(len);
        pre_m[0] = height[0];
        after_m[len-1] = height[len-1];
        for(int i=1;i<len;i++){
            pre_m[i] = max(pre_m[i-1],height[i]);
        }
        for(int i=len-2;i>=0;i--){
            after_m[i] = max(after_m[i+1],height[i]);
        }
        int ans=0;
        for(int i=0;i<len;i++){
            ans += min(pre_m[i],after_m[i]) - height[i];
        }
        return ans;
    }
};

优化空间复杂度至 O ( 1 ) O(1) O(1)
方法2: 单调栈 TODO
B站讲解

class Solution {
public:
    int trap(vector<int>& height) {
        int len = height.size();
        int ans=0;
        stack<int> st;
        for(int i=0;i<len;i++){
            while(!st.empty() && height[i]>height[st.top()]){//可以接水了
                int j=st.top();
                st.pop();
                if(st.empty()) break;
                int left = st.top();
                int dh = min(height[left],height[i]) - height[j];//min(当前,第二个栈顶)-栈顶
                ans += dh * (i-left-1);
            }
            st.push(i);
        }
        return ans;
    }
};
98. 验证二叉搜索树

给你一个二叉树的根节点 root ,判断其是否是一个有效的二叉搜索树。
有效 二叉搜索树定义如下:
节点的左子树只包含 小于 当前节点的数。
节点的右子树只包含 大于 当前节点的数。
所有左子树和右子树自身必须也是二叉搜索树。

class Solution {
    long pre = LONG_MIN;
public:
    bool isValidBST(TreeNode* root,long left=LONG_MIN,long right=LONG_MAX) {
         if(root==nullptr) return true;
         long x=root->val;
         return x>left && x<right && isValidBST(root->left,left,x) && isValidBST(root->right,x,right);
    }
    bool isValidBST(TreeNode* root){
        if(root == nullptr) return true;
        if(!isValidBST(root->left) || root->val <= pre)
            return false;
        pre = root->val; //中序遍历这时候 根节点
        return isValidBST(root->right);
    }
};

单调栈

739. 每日温度

给定一个整数数组 temperatures ,表示每天的温度,返回一个数组 answer ,其中 answer[i] 是指对于第 i 天,下一个更高温度出现在几天后。如果气温在这之后都不会升高,请在该位置用 0 来代替。

class Solution {
public:
    vector<int> dailyTemperatures(vector<int>& temperatures) {
        stack<int> st;
        int n = temperatures.size();
        vector<int> res(n);
        for(int i=0;i<n;i++){
            //栈是单减的
            while(!st.empty() && temperatures[i]>temperatures[st.top()]){
                int j = st.top();
                st.pop();
                res[j] = i - j;//栈顶 被pop()出去,才是下一个比它大的
            }
            st.push(i);
        }
        return res;
    }
};
class Solution {
    public int[] dailyTemperatures(int[] temperatures) {
        int n = temperatures.length;
        int[] ans = new int[n];
        int[] st = new int[n];
        int top = -1;
        for(int i = 0; i < n; i++){
            int t = temperatures[i];
            while(top >= 0 && t > temperatures[st[top]]){
                int j = st[top];
                top --;
                ans[j] = i - j;
            }
            top ++;
            st[top] = i;
        }
        return ans;
    }
}
496. 下一个更大元素 I

nums1 中数字 x 的 下一个更大元素 是指 x 在 nums2 中对应位置 右侧 的 第一个 比 x 大的元素。
给你两个 没有重复元素 的数组 nums1 和 nums2 ,下标从 0 开始计数,其中nums1 是 nums2 的子集。
对于每个 0 <= i < nums1.length ,找出满足 nums1[i] == nums2[j] 的下标 j ,并且在 nums2 确定 nums2[j] 的 下一个更大元素 。如果不存在下一个更大元素,那么本次查询的答案是 -1 。
返回一个长度为 nums1.length 的数组 ans 作为答案,满足 ans[i] 是如上所述的 下一个更大元素
在这里插入图片描述

class Solution {
public:
    vector<int> nextGreaterElement(vector<int>& nums1, vector<int>& nums2) {
        stack<int> st;
        int n = nums1.size();
        vector<int> ans(n,-1);
        unordered_map<int,int> idx;
        for(int i=0;i<nums1.size();i++){
            idx[nums1[i]]=i;
        }
        for(int i=nums2.size()-1;i>=0;i--){
            int x=nums2[i];
            while(!st.empty() && x >= st.top()){
                //由于 往前有一个更大的,所以当前栈顶肯定不会是左边元素的 下一个更大的元素,当前栈顶没有用
                st.pop();
            }
            if(!st.empty() && idx.contains(x)) {
                ans[idx[x]] = st.top();//这维护了一个单减的单调栈,比x小的等于的 都被pop出去了,所以栈顶就是下一个比他大的
            }
           st.push(x);
        }
        for(int i=0;i<nums2.size();i++){
            int x = nums2[i];
            while(!st.empty() && x>st.top()){//从左往右,比他大的数,单调栈是递增的
                ans[idx[st.top()]] = x;//x是栈顶的下一个更大元素
                st.pop();//构造递增单调栈
            }
            if(idx.contains(x)) //该题的特殊要求
                st.push(x);
        }
        return ans;
    }
};
1019. 链表中的下一个更大节点

给定一个长度为 n 的链表 head
对于列表中的每个节点,查找下一个 更大节点 的值。也就是说,对于每个节点,找到它旁边的第一个节点的值,这个节点的值 严格大于 它的值。
返回一个整数数组 answer ,其中 answer[i] 是第 i 个节点( 从1开始 )的下一个更大的节点的值。如果第 i 个节点没有下一个更大的节点,设置 answer[i] = 0 。

class Solution {
public:
    vector<int> nextLargerNodes(ListNode* head) {
        vector<int> ans;
        stack<pair<int,int>> s;
        ListNode* cur=head;
        int idx=-1;
        while(cur){
            idx++;
            ans.push_back(0);
            while(!s.empty() && cur->val > s.top().first){
                ans[s.top().second] = cur->val;//x是栈顶的下一个更大元素
                s.pop();//构造递增单调栈
            }
            s.emplace(cur->val,idx);
            cur=cur->next;
        }
        return ans;
    }
};

DFS

全排列

在这里插入图片描述

class Solution {
    vector<vector<int>> ans;
    vector<int> path;
    vector<int> st;
    void dfs(vector<int>& nums, int i){
        if(i == nums.size()) {
            ans.push_back(path);return;
        }
        for(int j = 0; j < nums.size(); j ++ ){
            if(!st[j]){
                path.push_back(nums[j]);
                st[j] = 1;
                dfs(nums, i + 1);
                path.pop_back();
                st[j] = 0;
            }
        }
    }
public:
    vector<vector<int>> permute(vector<int>& nums) {
        for(int i = 0; i < nums.size(); i++) st.push_back(0);
        dfs(nums, 0);
        return ans;
    }
};
47. 全排列 II

给定一个可包含重复数字的序列 nums ,按任意顺序 返回所有不重复的全排列。

输入:nums = [1,1,2]
输出:
[[1,1,2],
[1,2,1],
[2,1,1]]
示例 2:
输入:nums = [1,2,3]
输出:[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]

//st[j-1] == false是因为nums[j-1]在回溯的过程中被撤销选择
//candidates[j] == candidates[j - 1]是同层的,所以答案中仍然会有[1,1’,2]这样的,但是不会存在[1’,1,2]

class Solution {
    vector<vector<int>> ans;
    vector<int> path;
    vector<bool> st;
    void dfs(vector<int>& candidates, int u){
        if(u == candidates.size()) {
            ans.emplace_back(path);
            return;
        }
        
        for(int j = 0; j < candidates.size(); j++ ){
            if(!st[j]) {
                //st[j-1] == false是因为nums[j-1]在回溯的过程中被撤销选择
                //candidates[j] == candidates[j - 1]是同层的,所以答案中仍然会有[1,1',2]这样的,但是不会存在[1',1,2]
                if(j != 0 && candidates[j] == candidates[j - 1] && st[j-1] == false) 
                    continue;                
                st[j] = true;
                path.push_back(candidates[j]);//从左到右依次枚举每个数,每次将它放在一个空位上;
                dfs(candidates, u + 1);
                path.pop_back();
                st[j] = false;
            }
        }
    }
public:
    vector<vector<int>> permuteUnique(vector<int>& nums) {
        sort(nums.begin(), nums.end());//先将所有数从小到大排序,这样相同的数会排在一起; 因为结果是要去重的&candidates[j] == candidates[j - 1]
        st = vector<bool>(nums.size(), false);
        dfs(nums, 0);
        return ans;
    }
};

在这里插入图片描述

详细题解

回溯

子集回溯

在这里插入图片描述
递归不要想太多层(想太多反而把自己弄晕了),只需要把边界条件和非边界条件写对
在这里插入图片描述

17. 电话号码的字母组合

给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。答案可以按 任意顺序 返回。
给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。
在这里插入图片描述

78. 子集

给你一个整数数组 nums ,数组中的元素 互不相同 。返回该数组所有可能的
子集(幂集)。
解集 不能 包含重复的子集。你可以按 任意顺序 返回解集。

class Solution {
    string MAPPING[10] = {"","","abc","def","ghi","jkl","mno","pqrs","tuv","wxyz"};//用数组表示哈希表。数组比哈希表更快
    void dfs(string digits,int len,int idx,vector<string>& ans,string& path){
        if(idx == len){//边界条件
            ans.emplace_back(path);
            return;
        }
        for(char c:MAPPING[digits[idx] - '0']){
            path[idx] = c;
            dfs(digits,len,idx+1,ans,path);//下一个子问题
            //不需要返回现场 因为MAPPING没有受到影响没有改变,对于idx+1,还是可以从头选择for(char c:MAPPING[digits[idx] - '0'])
        }

    }
public:
    vector<string> letterCombinations(string digits) {
        int len = digits.size();
        if(len==0) return {};
        vector<string> ans;
        string path(len,0);//这个不初始化,后面path一直是""
        dfs(digits,len,0,ans,path);
        return ans;
    }
};

每一个数字都有(3−4)个字符选择,可以算成4;总共有 n 个字符,因此有 4 n 4^n 4n种选择(状态), 对于每一种选择(状态),都需要$O(n)的时间 append 到 ans, 因此是 O ( n ∗ 4 n ) O(n*4^n) O(n4n)

方法一:选谁不选谁的视角

class Solution {
    void dfs(int i, int len, vector<int>& nums, vector<int>& path, vector<vector<int>>& ans){
        if(i == len) {// 子集构造完毕
            ans.emplace_back(path);
            return;
        }

        dfs(i+1,len, nums, path, ans); // 不选 nums[i]

       
        path.push_back(nums[i]);
        dfs(i+1, len, nums, path, ans);
        path.pop_back();//恢复现场
        
    }
public:
    vector<vector<int>> subsets(vector<int>& nums) {
        int len = nums.size();
        vector<vector<int>> ans;
        vector<int> path;
        dfs(0,len, nums, path, ans);
        return ans;
    }
};

方法二: 放在哪个位置答案的视角【不保证有序】
相似子问题分解+递归
枚举子集(答案)的第一个数选谁,第二个数选谁,第三个数选谁,依此类推。

class Solution {
    void dfs(int i, int len, vector<int>& nums, vector<int>& path, vector<vector<int>>& ans){
        if(i == len) {// 子集构造完毕
            ans.emplace_back(path);
            return;
        }
//如果选 nums[j] 添加到 path 末尾,那么下一个要添加到 path 末尾的数,就要在 nums[j+1] 到 nums[n−1] 中枚举了。
        for(int j=i;j<len;j++){
            path.push_back(nums[j]);
            dfs(j+1,len,nums,path,ans);
            path.pop_back();
        }
    }
public:
    vector<vector<int>> subsets(vector<int>& nums) {
        int len = nums.size();
        vector<vector<int>> ans;
        vector<int> path;
        dfs(0,len, nums, path, ans);
        return ans;
    }
};
131. 分割回文串

给你一个字符串 s,请你将 s 分割成一些子串,使每个子串都是回文串。返回 s 所有可能的分割方案。

示例 1:
输入:s = “aab”
输出:[[“a”,“a”,“b”],[“aa”,“b”]]
示例 2:
输入:s = “a”
输出:[[“a”]]

1. 选不选谁的视角
在这里插入图片描述

class Solution {
    bool isPalindrome(string& s, int left, int right){
        while(left < right)//双向 双指针 判断s[left,right]是不是回文串
            if(s[left++] != s[right--])
                return false;
        return true;
    }
    //通过引入一个start变量解决
    void dfs(int i,int start,int len,vector<vector<string>>& ans,vector<string>& path,string& s){
        if(i == len) {
            ans.emplace_back(path);
            return;
        }
        //不选 i 和 i+1 之间的逗号
        if (i < len - 1)
        //i=n-1 时,也就是剩最后一个的时候 一定要选(因为是将S分割的,必须包含所有的 最后一个字母必须要选上)
            dfs(i+1,start,len,ans,path,s);

        if(isPalindrome(s, start, i)){//s[start,i]
            //选 i 和 i+1 之间的逗号
            path.push_back(s.substr(start,i-start+1));
            dfs(i+1,i+1,len,ans,path,s);//对于s,下一个子串的选择从s的i+1开始。所以还是一个一个 看选不选
            path.pop_back();//恢复现场
        }
    }
public:
    vector<vector<string>> partition(string s) {
        int len = s.length();
        vector<vector<string>> ans;
        vector<string> path;
        dfs(0,0,len,ans,path,s);
        return ans;
    }
};

时间复杂度: O ( n 2 n ) O(n2^n) O(n2n),其中 n 为 s 的长度。每次都是选或不选,递归次数为一个满二叉树的节点个数,那么一共会递归 O ( 2 n ) O(2^n) O(2n) 次(等比数列和),再算上判断回文和加入答案时需要 O(n) 的时间,所以时间复杂度为 O ( n 2 n ) O(n2^n) O(n2n)
空间复杂度: O ( n ) O(n) O(n)。返回值的空间不计。

2. 答案的视角(枚举子串结束位置)

class Solution {
    bool isPalindrome(string& s, int left, int right){
        while(left < right)//双向 双指针 判断s[left,right]是不是回文串
            if(s[left++] != s[right--])
                return false;
        return true;
    }
    void dfs(int i,int len,vector<vector<string>>& ans,vector<string>& path,string& s){
        if(i == len) {
            ans.emplace_back(path);
            return;
        }
        for(int j=i;j<len;j++){// 枚举子串的结束位置
            if(isPalindrome(s,i,j)){//s[i,j]
                path.push_back(s.substr(i,j-i+1));
                dfs(j+1,len,ans,path,s);//处理s[j+1]
                path.pop_back();//恢复现场
            }
        }
    }
public:
    vector<vector<string>> partition(string s) {
        int len = s.length();
        vector<vector<string>> ans;
        vector<string> path;
        dfs(0,len,ans,path,s);
        return ans;
    }
};

时间复杂度: O ( n 2 n ) O(n2^n) O(n2n),其中 n 为 s 的长度。答案的长度至多为逗号子集的个数,即 O ( 2 n ) O(2^n) O(2n),因此会递归 O ( 2 n ) O(2^n) O(2n) 次,再算上判断回文和加入答案时需要 O(n) 的时间,所以时间复杂度为 O ( n 2 n ) O(n2^n) O(n2n)
空间复杂度: O ( n ) O(n) O(n)。返回值的空间不计。

在这里插入图片描述

class Solution {
    vector<vector<string>> ans;
    vector<string> path;
    unordered_set<string> sset;
    void dfs(int i,int len,string& s){
        if(i == len){
            ans.emplace_back(path);
            return;
        }
        for(int j=i;j<len;j++){
            string str = s.substr(i,j-i+1);
            if(sset.find(str) == sset.end()){
                sset.insert(str);
                path.push_back(str);
                dfs(j+1,len,s);
                path.pop_back();
                sset.erase(str);
            }
        }
    }

public:
    int maxUniqueSplit(string s) {
        int len = s.length();
        int num = 0;
        dfs(0,len,s);
        for(int i=0;i<ans.size();i++){
            int n=ans[i].size();
            num = max(num,n);
        }
        return num;
    }
};
模板77. 组合

给定两个整数 n 和 k,返回范围 [1, n] 中所有可能的 k 个数的组合。
你可以按 任何顺序 返回答案。
在这里插入图片描述

class Solution {
    vector<int> path;
    vector<vector<int>> ans;
    void dfs(int k, int n, int idx){
        if (path.size() == k) { // 选好了
            ans.emplace_back(path);
            return;
        }
        for(int j = idx; j <= n; j++){ //模板循环j从当前下标开始
            path.push_back(j);
            dfs(k, n, j+1);//模板 j+1
            path.pop_back();
        }
    }
public:
    vector<vector<int>> combine(int n, int k) {
        dfs(k, n, 1);
        return ans;
    }
};

在这里插入图片描述
排序:// 这是因为数组已排序,后边元素更大,子集和一定超过 target。数组 candidates 有序,也是 深度优先遍历 过程中实现「剪枝」的前提
//将数组先排序的思路来自于这个问题:去掉一个数组中重复的元素。很容易想到的方案是:先对数组 升序 排序,重复的元素一定不是排好序以后相同的连续数组区域的第 1 个元素。

class Solution {
    vector<vector<int>> ans;
    vector<int> path;
    void dfs(vector<int>& candidates, int target, int i){
        if(target == 0) {
            ans.emplace_back(path);
            return;
        }
        
        for(int j = i; j < candidates.size(); j++ ){
            if(target - candidates[j] < 0) break;// 剪枝 不可能直接break不继续下去了
            path.push_back(candidates[j]);
            dfs(candidates, target - candidates[j], j); //这里是j的话,实现了 可以重复选;j+1是不可重复
            path.pop_back();
        }
    }
public:
    vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
        sort(candidates.begin(), candidates.end()); // 为啥要先排序呢?降序的话会报错
        dfs(candidates, target, 0);
        return ans;
    }
};
40. 组合总和 II

给定一个候选人编号的集合 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。
candidates 中的每个数字在每个组合中只能使用 一次 。
注意:解集不能包含重复的组合。

示例 1:
输入: candidates = [10,1,2,7,6,1,5], target = 8,
输出:
[
[1,1,6],
[1,2,5],
[1,7],
[2,6]
]

if(j > start && candidates[j] == candidates[j-1]) continue; 解释:
在这里插入图片描述

class Solution {
    vector<vector<int>> ans;
    vector<int> path;
    void dfs(vector<int>& candidates, int target, int start){
        if(target == 0) {
            ans.emplace_back(path);
            return;
        }
        
        for(int j = start; j < candidates.size(); j++ ){
            if(target - candidates[j] < 0) break;// 剪枝 不可能直接break不继续下去了
            if(j > start && candidates[j] == candidates[j-1]) continue;
            // j > start 跳过,[1,1,5]这种,后面遇到第二个1的时候没有continue跳过
            
            path.push_back(candidates[j]);
            dfs(candidates, target - candidates[j], j + 1); //只用一次
            path.pop_back();
        }
    }
public:
    vector<vector<int>> combinationSum2(vector<int>& candidates, int target) {
        sort(candidates.begin(), candidates.end()); // 为啥要先排序呢?降序的话会报错
        dfs(candidates, target, 0);
        return ans;
    }
};

排序

快排

时间: O ( n l o g n ) O(nlogn) O(nlogn)

void quick_sort(int q[], int l, int r)
{
    //递归的终止情况
    if(l >= r) return;

    //第一步:分成子问题
    int i = l - 1, j = r + 1, x = q[l + r >> 1];
    while(i < j)
    {
        do i++; while(q[i] < x);
        do j--; while(q[j] > x);
        if(i < j) swap(q[i], q[j]);
    }

    //第二步:递归处理子问题
    quick_sort(q, l, j), quick_sort(q, j + 1, r);

    //第三步:子问题合并.快排这一步不需要操作,但归并排序的核心在这一步骤
}

快速选择 O ( n ) O(n) O(n)

int quickSort(vector<int>& nums, int l, int r, int k) { //快速选择 第k个(id:k-1)
        if(l == r) return nums[k];
        int x = nums[l], i = l - 1, j = r + 1;
        while(i < j) {
            do i ++ ; while(nums[i] > x); //从大往小 降序排
            do j -- ; while(nums[j] < x);
            if(i < j) swap(nums[i], nums[j]);
        }
        if(k <= j)  return quickSort(nums, l, j, k);//已经确定第k大的元素在前半截 
        else return quickSort(nums, j+1, r, k);
        
    }
计数排序

时间复杂度 O ( n ) O(n) O(n)
hash是统计每个元素出现次数,s是统计出现次数的个数,比如[1,1,2,2,3] s[2]=2,s[1]=1。后面操作是,从出现次数最大的,累加到k

class Solution {
public:
    vector<int> topKFrequent(vector<int>& nums, int k) {
        unordered_map<int,int> hash;
        vector<int> res;
        for (int x : nums) hash[x] ++;
        int n = nums.size();
        vector<int>s(n + 1, 0);
        for (auto &p : hash) s[p.second] ++ ;
        int i = n, t = 0;
        while (t < k) t += s[i -- ];
        for (auto &p : hash)
            if (p.second > i)
                res.push_back(p.first);
        return res;
    }
};
215. 数组中的第K个最大元素

给定整数数组 nums 和整数 k,请返回数组中第 k 个最大的元素。

请注意,你需要找的是数组排序后的第 k 个最大的元素,而不是第 k 个不同的元素。

你必须设计并实现时间复杂度为 O(n) 的算法解决此问题。

class Solution {
public:
    int findKthLargest(vector<int>& nums, int k) {
        return quickSelect(nums, k);
    }
    int quickSelect(vector<int>& nums, int k) {
        // 基于快排的快速选择
        // 随机选择基准数字
        int p = nums[rand() % nums.size()];
        // 将大于等于小于的元素分别放入三个数组
        vector<int> big, equal, small;
        for (int a : nums) {
            if (a < p) small.push_back(a);
            else if (a == p) equal.push_back(a);
            else big.push_back(a);
        }
        // 第k大元素在big中, 递归划分
        if (k <= big.size()) {
            return quickSelect(big, k);
        }
        // 第k大元素在small中, 递归划分
        if (big.size() + equal.size() < k) {
            return quickSelect(small, k - (big.size() + equal.size()));
        }
        // 第k大元素在equal中, 返回p
        return p;
    }
};

链表

21合并两个链表

将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。
递归:

class Solution {
    public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
        if (l1 == null) {
            return l2;
        } else if (l2 == null) {
            return l1;
        } else if (l1.val < l2.val) {
            l1.next = mergeTwoLists(l1.next, l2); //
            return l1; //
        } else {
            l2.next = mergeTwoLists(l1, l2.next);
            return l2;
        }
    }
}

作者:力扣官方题解
链接:https://leetcode.cn/problems/merge-two-sorted-lists/solutions/226408/he-bing-liang-ge-you-xu-lian-biao-by-leetcode-solu/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

迭代:

/**
 * 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* list1, ListNode* list2) {
        ListNode* pre = new ListNode(-1);
        ListNode* res = pre;
        while(list1 != nullptr && list2 != nullptr){
            if(list1->val < list2->val) {
                res->next = list1;
                list1 = list1->next;
            }
            else{
                res->next = list2;
                list2 = list2->next;
            }
            res = res->next;
        }
        if(list1 != nullptr) res->next = list1;
        else res->next = list2;
        return pre->next;
    }
};

原地交换

限制条件:时间复杂度 O ( n ) O(n) O(n),空间复杂度 O ( 1 ) O(1) O(1) 允许修改数组

LCR 120. 寻找文件副本

设备中存有 n 个文件,文件 id 记于数组 documents。若文件 id 相同,则定义为该文件存在副本。请返回任一存在副本的文件 id。
提示:
0 ≤ documents[i] ≤ n-1
2 <= n <= 100000

示例 1:
输入:documents = [2, 5, 3, 0, 5, 0]
输出:0 或 5

在一个长度为 n 的数组 nums里的所有数字都在 0 ~ n-1 的范围内 。 此说明含义:数组元素的 索引 和 值 是 一对多 的关系。因此,可遍历数组并通过交换操作,使元素的 索引 与 值一一对应(即 n u m s [ i ] = i nums[i]=i nums[i]=i。因而,就能通过索引映射对应的值,起到与字典等价的作用

对于 i i i,一直交换swap(nums[i],nums[nums[i]]),(该交换操作保证 nums[i]位置上一定是nums[i]
直至nums[i]=i (i位置上是i)或者找到重复的元素 i++
找到重复元素:nums[nums[i]]==nums[i] && nums[i] != i第一次出现是在i位置,第二次出现是在nums[i]位置,所以返回nums[i]

class Solution {
public:
    int findRepeatDocument(vector<int>& nums) {
        int i=0;
        while(i<nums.size()){
            if(nums[i]!=i) {// 对于i,一直交换,直至nums[i]=i或者找到重复的元素
                if(nums[nums[i]]!=nums[i])//因为都在[0,n)之间,不会越界
                    swap(nums[i],nums[nums[i]]);
                //如果nums[x]==x,这是x第二次出现了,第一次是nums[i]=x
                //举例子:[3,?,?,3,;;] nums[0]=3=nums[3]=nums[nums[0]] 
                else return nums[i];
            }
            else i++;
        }
        return -1;
    }
};
41. 缺失的第一个正数

给你一个未排序的整数数组 nums ,请你找出其中没有出现的最小的正整数。
请你实现时间复杂度为 O(n) 并且只使用常数级别额外空间的解决方案。

输入:nums = [1,2,0]
输出:3
解释:范围 [1,2] 中的数字都在数组中。
示例 2:
输入:nums = [3,4,-1,1]
输出:2
解释:1 在数组中,但 2 没有。

class Solution {
public:
    int firstMissingPositive(vector<int>& nums) {
        int len = nums.size();
        for(int i = 0; i < len; i++) {
            while(nums[i] != i + 1 && nums[i] > 0 && nums[i] <= len && nums[i] != nums[nums[i]-1]) 
                swap(nums[i],nums[nums[i] - 1]);
        }

        for(int i = 0; i < len; i++) {
            if(nums[i] != i + 1) return i + 1;
        }
        return len + 1;
    }
};

快慢指针

限制条件:时间复杂度 O ( n ) O(n) O(n),空间复杂度 O ( 1 ) O(1) O(1) 不允许修改数组

287. 寻找重复数

给定一个包含 n + 1 个整数的数组 nums ,其数字都在 [1, n] 范围内(包括 1 和 n),可知至少存在一个重复的整数。
假设 nums 只有 一个重复的整数 ,返回 这个重复的数 。
你设计的解决方案必须 不修改 数组 nums 且只用常量级 O(1) 的额外空间
不能修改数组,题目给的特殊的数组当作一个链表来看,数组的下标就是指向元素的指针,把数组的元素也看作指针。如 0 是指针,指向 nums[0],而 nums[0] 也是指针,指向 nums[nums[0]].

int point = 0;
while(true){
    point = nums[point]; // 等同于 next = next->next; 
}

链表中的环:
在这里插入图片描述
slow 和 fast 会在环中相遇,先假设一些量:起点到环的入口长度为 m,环的周长为 c,在 fast 和 slow 相遇时 slow 走了 n 步。则 fast 走了 2n 步,fast 比 slow 多走了 n 步,而这 n 步全用在了在环里循环(n%c==0)。
当 fast 和 last 相遇之后,我们设置第三个指针 finder,它从起点开始和 slow(在 fast 和 slow 相遇处)同步前进,当 finder 和 slow 相遇时,就是在环的入口处相遇,也就是重复的那个数字相遇。

为什么 finder 和 slow 相遇在入口
fast 和 slow 相遇时,slow 在环中行进的距离是 n-m,其中 n%c==0。这时我们再让 slow 前进 m 步——也就是在环中走了 n 步了。而 n%c==0 即 slow 在环里面走的距离是环的周长的整数倍,就回到了环的入口了,而入口就是重复的数字。
我们不知道起点到入口的长度 m,所以弄个 finder 和 slow 一起走,他们必定会在入口处相遇。finder走了m步刚好到入口,slow和finder走的步数一样,也是m,slow走了n+m,也刚好到入口。相遇

链接:https://leetcode.cn/problems/find-the-duplicate-number/solutions/18952/kuai-man-zhi-zhen-de-jie-shi-cong-damien_undoxie-d/

class Solution {
public:
//1.将数组等价成链表,快指针走2步慢指针每次1步 直至相遇: n%c==0
//2.再定义finder,从入口开始走直至和slow相遇。目的是使slow前进m步,(相遇时finder肯定走了m步)
    int findDuplicate(vector<int>& nums) {
        int fast = 0;
        int slow = 0;
        while(true){
            fast = nums[nums[fast]];
            slow = nums[slow];
            if(fast == slow) break;
        }
        int finder = 0;
        while(finder != slow){
            slow = nums[slow];
            finder = nums[finder];
            if(finder == slow) break;
        }
        return slow;
    }
};

异或

136. 只出现一次的数字

给你一个 非空 整数数组 nums ,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。

你必须设计并实现线性时间复杂度的算法来解决此问题,且该算法只使用常量额外空间
a^0 = a; a^a = 0
a1^a2^a3^a4^a5^a6...^a_{2m} ^a_{2m+1} = a_{2m+1} 假设a1-2m中每个元素出现两次,a2m+1出现一次

class Solution {
public:
    int singleNumber(vector<int>& nums) {
        int ans = 0;
        for(int num : nums){
            ans = ans ^ num;
        }
        return ans;
    }
};

二分

第一段满足一个性质,第二段都不满足该性质,通过二分找出边界点(满足该性质的最后一个数)

bool check(int x) {/* ... */} // 检查x是否满足某种性质
// lower_bound 返回最小的满足 nums[i] >= target 的 i
def lower_bound(nums: List[int], target: int) -> int:
    left, right = 0, len(nums) - 1  # 闭区间 [left, right]
    while left <= right:   区间不为空# 闭区间写法
        # 循环不变量:
        # nums[left-1] < target
        # nums[right+1] >= target
        mid = (left + right) // 2
        if nums[mid] < target:
            left = mid + 1  # 范围缩小到 [mid+1, right]
        else:
            right = mid - 1  # 范围缩小到 [left, mid-1]
    return left  # 或者 right+1

// 区间[l, r]被划分成[l, mid]和[mid + 1, r]时使用:
int bsearch_1(int l, int r)
{
    while (l < r)
    {
        int mid = l + r >> 1;
        if (nums[mid] >= target) r = mid;    // 找>=target的第一个
        else l = mid + 1;
    }
    return l;
}

// 区间[l, r]被划分成[l, mid - 1]和[mid, r]时使用:
int bsearch_2(int l, int r)
{
    while (l < r)
    {
        int mid = l + r + 1 >> 1;
        if (check(mid)) l = mid;
        else r = mid - 1;
    }
    return l;
}
69. x 的平方根

给你一个非负整数 x ,计算并返回 x 的 算术平方根 。
由于返回类型是整数,结果只保留 整数部分 ,小数部分将被 舍去 。
注意:不允许使用任何内置指数函数和算符,例如 pow(x, 0.5) 或者 x ** 0.5 。

class Solution {
public:
    int mySqrt(int x) {
        if(x == 0) return 0;
        if(x < 4) return 1;
        int left = 0;
        int right = x - 1;
        while(left < right){
            int mid = (left + right) / 2;
             // 注意:这里为了避免乘法溢出,改用除法
            if(mid > x / mid) {
                right = mid;
            }
            else{
                left = mid + 1;
            }
        }
        return left - 1;//以前返回left的时候是left和right是数组的索引,这次是数,不一样
    }
};

在这里插入图片描述

两次二分:

第一次二分:定位到所在行(从上往下,找到最后一个满足 mat[x]][0] <= t 的行号)
第二次二分:从所在行中定位到列(从左到右,找到最后一个满足 mat[row][x] <= t 的列号)
要先找行。因为target可能大于第0行的最大的,先遍历会找不到
在这里插入图片描述

class Solution {
public:
    bool searchMatrix(vector<vector<int>>& matrix, int target) {
        int m = matrix.size();
        int n = matrix[0].size();
        int rl = 0, rr = m - 1;
        int cl = 0, cr = n -1;
        while(rl < rr){
            int rmid = (rl + rr + 1) >> 1;
            if(matrix[rmid][0] <= target) rl = rmid;
            else   rr = rmid - 1;
        }
        int row = rl;
        if(matrix[row][0] == target) return true;
        if(matrix[row][0] > target) return false;

        while(cl < cr){
            int cmid = (cl + cr + 1) >> 1;
            if(matrix[row][cmid] <= target) cl = cmid;
            else  cr = cmid - 1;
        }
        int col = cl;
        return matrix[row][col] == target;
    }
};

改进成一次二分查找,利用性质:一维和二维矩阵的对应

bool searchMatrix(vector<vector<int>>& matrix, int target) {
        int m = matrix.size();
        int n = matrix[0].size();
        int l = 0, r = m*n - 1;
        while(l < r){
            int rmid = (l + r + 1) >> 1;
            if(matrix[rmid/n][rmid%n] <= target) l = rmid;
            else   r = rmid - 1;
        }
        if(matrix[l/n][l%n] == target) return true;
        return false;
    }
240. 搜索二维矩阵 II

编写一个高效的算法来搜索 m x n 矩阵 matrix 中的一个目标值 target 。该矩阵具有以下特性:
每行的元素从左到右升序排列。
每列的元素从上到下升序排列。

因为没有下一行肯定比上一行都大这个性质,用不了二次二分
在这里插入图片描述
使用二分的话,时间复杂度:O(mlogn)。要对每一行使用二分查找的时间复杂度为 O(logn),最多需要进行 m 次二分查找。
抽象BST,也是Z 字形查找: 每次搜索可以排除一行或一列的元素。
在这里插入图片描述

class Solution {
public:
    bool searchMatrix(vector<vector<int>>& matrix, int target) {
        int m = matrix.size();
        int n = matrix[0].size();
        for(int i = 0, j = n -1; i < m && j >= 0; ){
            if(matrix[i][j] > target)   j--;
            else if(matrix[i][j] < target) i++;
            else return true;
        }
        return false;
    }
};

动态规划(贪心)

45.跳跃游戏 II
给定一个长度为 n 的 0 索引整数数组 nums。初始位置为 nums[0]。
每个元素 nums[i] 表示从索引 i 向前跳转的最大长度。换句话说,如果你在 nums[i] 处,你可以跳转到任意 nums[i + j] 处:

0 <= j <= nums[i]
i + j < n

返回到达 nums[n - 1] 的最小跳跃次数。生成的测试用例可以到达 nums[n - 1]。
在这里插入图片描述

class Solution {
public:
    int jump(vector<int>& nums) {
        int n = nums.size();
        vector<int> f(n);
        for(int i = 1, last = 0; i < n; i ++ ) {
            while(i > last + nums[last]) last ++ ;//后面的i无法经由last跳到了(这里都是位置,下标)
            f[i] = f[last] + 1;//f[i]:从0到i需要的最短步数
            //if(last == i) return false; 说明无法跳到i位置了,也就无法跳到最后
        }
        return f[n-1];
    }
};

在这里插入图片描述

迷宫问题爆搜模板

在这里插入图片描述

class Solution {
    bool dfs(vector<vector<char>>& board, int u, string word, int i, int j) {
        if(board[i][j] != word[u]) return false;
        if(u == word.size() - 1) 
            return true;
        int X[4] = {1, 0, -1,0}, Y[4] = {0, 1, 0, -1};
        char tmp = board[i][j];
        board[i][j] = '*';
        for(int t = 0; t < 4; t ++ ) {
            int nx = i + X[t], ny = j + Y[t];
            if(nx>=0 && ny>=0 && nx<board.size() && ny<board[0].size()) {
                if(dfs(board, u + 1, word, nx, ny)) return true;
            }
        }
        board[i][j] = tmp; 
        return false;
    }
public:
    bool exist(vector<vector<char>>& board, string word) {
        for(int i = 0; i < board.size(); i ++ ) 
            for(int j = 0; j < board[0].size(); j ++ ) 
                if(dfs(board, 0, word, i, j))
                    return true;
        return false;
    }
};
200.岛屿数量

还是像爆搜一样的遍历,找到1,然后dfs,将所有相连的1变为0,找完所有相连的返回。
【注意 这个不用恢复现场】

class Solution {
    bool dfs(vector<vector<char>>& grid, int x, int y) {
        if(grid[x][y] == '0')  return false;
        int X[4] = {1,0,-1,0}, Y[4] = {0,1,0,-1};
        grid[x][y] = '0';
        for(int i = 0; i < 4; i ++ ) {
            int a = x + X[i], b = y + Y[i];
            if(a >= 0 && a < grid.size() && b >= 0 && b < grid[0].size()) {
                if(dfs(grid, a, b)) return true;
            }
        }
        return false;
    }
public:
    int numIslands(vector<vector<char>>& grid) {
        int ans = 0;
        for(int i = 0; i < grid.size(); i ++ ) 
            for(int j = 0; j < grid[0].size(); j ++) 
                if(grid[i][j] == '1') {
                    ans ++;
                    dfs(grid, i, j);
                }
            
        return ans;
    }
};
求面积
public int maxAreaOfIsland(int[][] grid) {
    int res = 0;
    for (int r = 0; r < grid.length; r++) {
        for (int c = 0; c < grid[0].length; c++) {
            if (grid[r][c] == 1) {
                int a = area(grid, r, c);
                res = Math.max(res, a);
            }
        }
    }
    return res;
}

int area(int[][] grid, int r, int c) {
    if (!inArea(grid, r, c)) {
        return 0;
    }
    if (grid[r][c] != 1) {
        return 0;
    }
    grid[r][c] = 2;
    
    return 1 
        + area(grid, r - 1, c)
        + area(grid, r + 1, c)
        + area(grid, r, c - 1)
        + area(grid, r, c + 1);
}

boolean inArea(int[][] grid, int r, int c) {
    return 0 <= r && r < grid.length 
        	&& 0 <= c && c < grid[0].length;
}

题解类似的题

BFS

class Solution {
    int ans = -1;
    int fresh = 0;
    void bfs(vector<vector<int>> &grid){
        queue<pair<int, int>> q;
        //多源BFS
        for(int i = 0; i < grid.size(); i ++) 
            for(int j = 0; j < grid[0].size(); j ++){
                if(grid[i][j] == 2) q.push({i,j});
                if(grid[i][j] == 1) fresh ++;
            } 
    
        int dx[4] = {1,0,-1,0}, dy[4] = {0,1,0,-1};

        while(!q.empty()) {
            ans ++; //为啥以-1开始,这时候是0分钟
            int sz = q.size();
            while(sz--) {
                auto t = q.front(); q.pop();
                for(int i = 0; i < 4; i ++) {
                    int x = t.first + dx[i], y = t.second + dy[i];
                    if(x < 0 || x >= grid.size() || y < 0 || y >= grid[0].size()) continue;
                    if(grid[x][y] == 1) {
                        fresh --;
                        q.push({x,y}); grid[x][y] = 2; 
                    }
                }
                
            }
        }
    }
public:
    int orangesRotting(vector<vector<int>>& grid) {
        bfs(grid);
        return fresh ? -1 : max(ans, 0);;
    }
};

在这里插入图片描述

拓扑排序
class Solution {
public:
    bool canFinish(int numCourses, vector<vector<int>>& prerequisites) {
        int n = prerequisites.size();
        vector<int> result;
        vector<int> inDegree(numCourses, 0);//记录入度数
        unordered_map<int, vector<int>> umap;//数据结构中的啥表啊
        for (int i = 0 ; i < n ; i ++ ) {
            inDegree[prerequisites[i][1]] ++;
            umap[prerequisites[i][0]].push_back(prerequisites[i][1]);
        }
        queue<int> que;
        for (int i = 0 ; i < numCourses ; i ++ ) {
            if (inDegree[i] == 0) {
                que.push(i);
            }
        }

        while(que.size()) {
            int cur = que.front();
            que.pop();
            result.push_back(cur);
            vector<int> files = umap[cur];
            if (files.size()) {
                for (int i = 0 ; i < files.size() ; i ++ ) {
                    inDegree[files[i]] --;
                    if (inDegree[files[i]] == 0) que.push(files[i]);
                }
            }
        }
        if (result.size() == numCourses) {
            return true;
        }else {
            return false;
        }
    }
};

法2:DFS是否有环

class Solution {
    bool dfs(unordered_map<int, vector<int>>& mPre, vector<int>& inDegree, vector<int> mVisit, int i) {
        if(mVisit[i] == 1) return false;
        if(inDegree[i] == 0) return true;
        mVisit[i] = 1;//这错了,你咋知道i就可以被访问(即i的入读不一定是0) 怎么改啊
        for(int k = 0; k < mPre[i].size(); k ++ ) {
            inDegree[mPre[i][k]]--;
            if(!dfs(mPre, inDegree, mVisit, mPre[i][k])) return false;
        }
        mVisit[i] = 0;
        return true;
    }
public:
    bool canFinish(int numCourses, vector<vector<int>>& prerequisites) {
        vector<int> m_visit(numCourses,0);
        vector<int> inDegree(numCourses, 0);//记录入度数
        unordered_map<int, vector<int>> m_pre;
        for(int i = 0; i < prerequisites.size(); i ++) {
            m_pre[prerequisites[i][0]].push_back(prerequisites[i][1]); //[1]<-[0]
            inDegree[prerequisites[i][1]]++;
        }
        for(int i = 0; i< numCourses; i ++ ) {
            if(!dfs(m_pre, inDegree, m_visit, i)) return false;
        }
        return true;
    }
};

#TODO: 上面法2哪里错了

有三种方式判断图内是否有环,拓扑排序,dfs,和union and find:
dfs相当于判定有没有回溯边,并查集的两端如果已经在同一集合也说明有环
并查集不能用于判断有向图存在环,只能用于无向图

背包

在这里插入图片描述

01背包

2915. 和为目标值的最长子序列的长度

给你一个下标从 0 开始的整数数组 nums 和一个整数 target 。
返回和为 target 的 nums 子序列中,子序列 长度的最大值 。如果不存在和为 target 的子序列,返回 -1 。
子序列 指的是从原数组中删除一些或者不删除任何元素后,剩余元素保持原来的顺序构成的数组。

class Solution {
public:
    int lengthOfLongestSubsequence(vector<int>& nums, int target) {
        //0-1背包问题,f[i][j]表示从前i个物品中选,总量刚好为j 情况下选择的最多物品数量(每件只能选一次)
        // f[i][j] = max(f[i-1][j], f[i-1][j-nums[i]]+1) f[i-1][j]:不选第i个物品

        vector<vector<int>> f(nums.size()+1, vector<int>(target+1, INT_MIN));
        //初始值设为INT_MIN,是让f(j)里的值如果大于0,那肯定是从f(0)递归来的,就是题目说的要完全填满背包的意思。如果不设定这个,是可以全部设为0,表示最多能装几个数字~
        f[0][0] = 0;
        for(int i = 0; i < nums.size(); i ++ ) {
            for(int j = 0; j <= target; j ++ ) {
                f[i+1][j] = f[i][j];
                if(j >= nums[i]) f[i+1][j] = max(f[i][j], f[i][j-nums[i]]+1);  f[i+1][j] = max(f[i][j], f[i][j-nums[i]]+1) 否则nums[i]会溢出
            }
        }
        int res = f[nums.size()][target];
        return res > 0 ? res: -1;
    }
};

优化:

class Solution {
public:
    int lengthOfLongestSubsequence(vector<int>& nums, int target) {
        vector<int> f(target+1, INT_MIN);
        f[0] = 0;
        for(int i = 0; i < nums.size(); i ++ ) {
            for(int j = target; j >= nums[i]; j -- ) { //逆序 从target到nums[i]
                f[j] = max(f[j], f[j-nums[i]]+1); 
            }
        }
        int res = f[target];
        return res > 0 ? res: -1;
    }
};

二维01背包:设 f(t,i,j)表示考虑了前 t 个物品,体积 为 i 重量为 j 所能得到最大价值

signed main () {
    cin >> n >> V >> M;
    for (int i = 1; i <= n; i ++) {
        cin >> v[i] >> m[i] >> w[i];//体积,重量,价值
    }
    for (int i = 1; i <= n; i ++)
        for (int j = V; j >= v[i]; j --)
            for (int k = M; k >= m[i]; k --)
                f[j][k] = max (f[j - v[i]][k - m[i]] + w[i], f[j][k]);//动态转移方程,01 背包的思路
    cout << f[V][M];
} 

完全背包

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值