两天速通力扣HOT100[DAY2] (55~100)

两天速通力扣HOT100[DAY2] (55~100)

本题解旨在以最简单的语言总结hot100各题思路,为每一题提供一个思考入口,但想要手撕出来,需要自己认真推理细节。

2024-10-04日晚:86~95题没做,62题没想明白代码,两天速通还是失败了,明天再做吧。。。
欲速则不达,很多完成了的题依然感觉很心虚,再来一遍还是做不出来。建议踏踏实实看题解,自己能手撕出来才是真的。

目录

回溯 55~62

二分查找 63~68

栈 69~73

堆 74~76

贪心 77~80

动态规划 81~90

多维动态规划 91~95

技巧 96~100

55、全排列

思路

回溯基本思想:DFS + 状态还原

面对前方n种选择的时候,循环选择其中一种,做出对应的改变并进入下一层,退出时恢复原状

code
class Solution {
public:
    vector<vector<int>> permute(vector<int>& nums) {
        dfs(nums, 0);
        return res;
    }
private:
    vector<vector<int>> res;
    void dfs(vector<int> nums, int x) {
        if (x == nums.size() - 1) {
            res.push_back(nums);      // 添加排列方案
            return;
        }
        for (int i = x; i < nums.size(); i++) {
            swap(nums[i], nums[x]);   // 交换,将 nums[i] 固定在第 x 位
            dfs(nums, x + 1);         // 开启固定第 x + 1 位元素
            swap(nums[i], nums[x]);   // 恢复交换
        }
    }
};

作者:Krahets
链接:https://leetcode.cn/problems/permutations/solutions/2363882/46-quan-pai-lie-hui-su-qing-xi-tu-jie-by-6o7h/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

56、子集

思路

回溯暴力

code
class Solution {
public:
    vector<int> t;
    vector<vector<int>> ans;

    void dfs(int cur, vector<int>& nums) {
        if (cur == nums.size()) { // 递归终点
            ans.push_back(t);
            return;
        }
        t.push_back(nums[cur]); // 选择当前元素
        dfs(cur + 1, nums); // 进入下一层
        t.pop_back(); // 不选择当前元素
        dfs(cur + 1, nums); // 进入下一层
    }

    vector<vector<int>> subsets(vector<int>& nums) {
        dfs(0, nums);
        return ans;
    }
};

57、电话号码的字母组合

思路

回溯暴力

每个键遍历选择某个字母,进入下一个键

code
class Solution {
private:
    vector<string> m = {
        "abc","def", "ghi", "jkl", "mno", "pqrs", "tuv", "wxyz"
    };
    string digits;
public:
    void dfs(vector<string>& res, string& one, int index) {
        if(index == digits.size()) {
            res.push_back(one);
            return ;
        }
        int t = digits[index] - '0' - 2;
        for(int i = 0; i < m[t].size(); i++) {
            one += m[t][i];
            dfs(res, one, index + 1);
            one.pop_back();
        }
    }

    vector<string> letterCombinations(string digits) {
        vector<string> res;
        if(digits.empty()) return res;
        string one = "";
        this->digits = digits;

        dfs(res, one, 0);

        
        return res;
    }
};

58、组合总和

思路

回溯:对每个数字有选or不选两种选择,维护好每种情况下剩余target,回溯遍历即可。

剪枝:由于每个数字都是正数,所以当target < 0时无需继续遍历,直接return即可。

code
class Solution {
public:
    void dfs(vector<int>& candidates, int target, vector<vector<int>>& ans, vector<int>& combine, int idx) {
        if (idx == candidates.size() || target < 0) {
            return;
        }
        if (target == 0) {
            ans.emplace_back(combine);
            return;
        }
        // 直接跳过
        dfs(candidates, target, ans, combine, idx + 1);
        // 选择当前数
        if (target - candidates[idx] >= 0) {
            combine.emplace_back(candidates[idx]);
            dfs(candidates, target - candidates[idx], ans, combine, idx);
            combine.pop_back();
        }
    }

    vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
        vector<vector<int>> ans;
        vector<int> combine;
        dfs(candidates, target, ans, combine, 0);
        return ans;
    }
};

59、括号生成

思路

记录已放置左右括号的数量,当左括号数量小于n时可以放左括号,当右括号数量小于左括号数量时可以放右括号,根据数量来判断有几种选择,对每种选择回溯遍历即可。

code
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;
        }
        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;
    }
};

60、单词搜索

思路

从多个入口出发,回溯遍历

code
class Solution {
public:
    int m, n;
    vector<vector<int>> used;

    bool inBoard(int i, int j) {
        return 0 <= i && i < m && 0 <= j && j < n;
    }

    bool dfs(vector<vector<char>>& board, string word, int index, int i, int j) {
        if(index == word.size() - 1) return true;

        bool res = false;
        if(inBoard(i - 1, j) && board[i-1][j] == word[index + 1] && used[i-1][j] == 0) {
            used[i-1][j] = 1;
            res |= dfs(board, word, index + 1, i - 1, j);
            used[i-1][j] = 0;
        }
        if(inBoard(i, j - 1) && board[i][j-1] == word[index + 1] && used[i][j-1] == 0) {
            used[i][j-1] = 1;
            res |= dfs(board, word, index + 1, i, j - 1);
            used[i][j-1] = 0;
        }
        if(inBoard(i + 1, j) && board[i+1][j] == word[index + 1] && used[i+1][j] == 0) {
            used[i+1][j] = 1;
            res |= dfs(board, word, index + 1, i + 1, j);
            used[i+1][j] = 0;
        }
        if(inBoard(i, j + 1) && board[i][j+1] == word[index + 1] && used[i][j+1] == 0) {
            used[i][j+1] = 1;
            res |= dfs(board, word, index + 1, i, j + 1);
            used[i][j+1] = 0;
        }

        return res;

    }

    bool exist(vector<vector<char>>& board, string word) {
        this->m = board.size();
        this->n = board[0].size();
        vector<int> line(n);
        used.resize(m, line);
        bool res = false;
        for(int i = 0; i < m; ++i) {
            for(int j = 0; j < n; ++j) {
                if(board[i][j] == word[0]){
                    used[i][j] = 1;
                    res |= dfs(board, word, 0, i, j);
                    used[i][j] = 0;
                }
            }
        }
        return res;
    }
};

61、分割回文串

思路

回溯 + 动态规划预处理

为求出所有可能,需要回溯遍历,思路如下:

当遍历到i位置时,认为前0~i-1的字符串已分割完毕,从i出发寻找j,使得s[i:j]也是回文串,这时有两种选择:

  1. s[i:j]加入结果集,跳到j+1位置继续遍历
  2. 忽略,继续j++寻找新的回文串右边界

如此便形成标准回溯模式。

优化:每次判断s[i:j]是否为回文串耗时,可使用动态规划预处理,将判断某一段是否为回文串提前计算出来,后续直接取用即可。

dp[i][j]表示字符串s[i:j]是否为回文串,是取1,否取0,递推式如下:
d p [ i ] [ j ] = { t r u e i ≥ j d p [ i + 1 , j − 1 ] & ( s [ i ] = = s [ j ] ) i < j \begin{equation} dp[i][j] =\left\{ \begin{array}{ll} true & i \geq j \\ dp[i+1, j-1] \& (s[i] == s[j]) & i < j \end{array}\right. \end{equation} dp[i][j]={truedp[i+1,j1]&(s[i]==s[j])iji<j

code
class Solution {
private:
    vector<vector<int>> f;
    vector<vector<string>> ret;
    vector<string> ans;
    int n;

public:
    void dfs(const string& s, int i) {
        if (i == n) {
            ret.push_back(ans);
            return;
        }
        for (int j = i; j < n; ++j) {
            if (f[i][j]) {
                ans.push_back(s.substr(i, j - i + 1));
                dfs(s, j + 1);
                ans.pop_back();
            }
        }
    }

    vector<vector<string>> partition(string s) {
        n = s.size();
        f.assign(n, vector<int>(n, true));

        for (int i = n - 1; i >= 0; --i) {
            for (int j = i + 1; j < n; ++j) {
                f[i][j] = (s[i] == s[j]) && f[i + 1][j - 1];
            }
        }

        dfs(s, 0);
        return ret;
    }
};

62、N皇后

思路

N个皇后需要放置在NN列中,可以将结果保存为一个长度为N的数组,记录第i行的皇后放置在哪一列。对第一行来说,有N种选择,第二行有N-1种,第三行有N-3种,回溯遍历,遍历途中判断是否违背了斜线规则。

code

暂时没想明白,改天再写

63、搜索插入位置

思路

二分查找,个人感觉左闭右闭的边界控制好写

code
class Solution {
public:
    int searchInsert(vector<int>& nums, int target) {
        int left = 0, right = nums.size() - 1;
        while(left <= right) {
            int mid = (left + right) >> 1;
            if(nums[mid] == target) {
                return mid;
            } else if(nums[mid] > target) {
                right = mid - 1;
            } else {
                left = mid + 1;
            }
        }
        return left;
    }
};

64、搜索二维矩阵

思路
  1. 一次二分查找定位所在行,一次二分查找定位到所在列
  2. 该二维矩阵每行头尾拼接可以降为一维数组,按一维的二分查找即可
code
class Solution {
public:
    bool searchMatrix(vector<vector<int>>& matrix, int target) {
        int m = matrix.size(), n = matrix[0].size();
        int left = 0, right = m * n - 1;
        while(left <= right) {
            int mid = (left + right) >> 1;
            int r = mid / n, c = mid % n;
            if(matrix[r][c] == target) {
                return true;
            } else if(matrix[r][c] > target) {
                right = mid - 1;
            } else {
                left = mid + 1;
            }
        }
        return false;
    }
};

65、在排序数组中查找元素的第一个和最后一个位置

思路

二分查找在找到目标元素时,是直接返回;若想找到第一个和位置,只需在mid找到时,将mid排除出查找区间继续查找即可,直到left == right == mid

code
class Solution {
public:
    vector<int> searchRange(vector<int>& nums, int target) {
        int left = 0, right = nums.size() - 1;
        int first = -1, last = -1;
        while(left <= right) {
            int mid = (left + right) >> 1;
            if(nums[mid] == target) {
                first = mid;
                right = mid - 1; // 将mid排除出查找区间继续查找
            } else if(nums[mid] > target) {
                right = mid - 1;
            } else {
                left = mid + 1;
            }
        }
        left = 0; right = nums.size() - 1;
        while(left <= right) {
            int mid = (left + right) >> 1;
            if(nums[mid] == target) {
                last = mid;
                left = mid + 1; // 将mid排除出查找区间继续查找
            } else if(nums[mid] > target) {
                right = mid - 1;
            } else {
                left = mid + 1;
            }
        }
        return {first, last};
    }
};

66、搜索旋转排序数组

思路
  1. 一次二分找到最小的位置,也就是旋转点;再按逻辑二分查找,将旋转数组映射到升序数组
  2. 根据二分位置的值判断哪边是右序的,再判断target位于有序的一边还是无序的一边。有序的就正常二分,无序的就去碰碰运气。
code
class Solution {
public:
    int search(vector<int>& nums, int target) {
        int min = 0, n = nums.size();
        for(int l = 1, r = n - 1; l <= r; ) {
            int mid = (l + r) >> 1;
            if(nums[0] < nums[mid]) l = mid + 1;
            else {
                r = mid - 1;
                min = mid;
            }
        }
        for(int l = min, r = l + n - 1; l <= r; ) {
            int mid = (l + r) >> 1;
            int i = mid % n;
            if(target < nums[i]) r = mid - 1;
            else if(target > nums[i]) l = mid + 1;
            else return i;
        }
        return -1;
    }
};

67、寻找旋转排序数组中的最小值

思路

同上题

code
class Solution {
public:
    int findMin(vector<int>& nums) {
        if(nums[nums.size() - 1] > nums[0]) return nums[0];
        int res = 0;
        int left = 1, right = nums.size() - 1; // left 从 1 开始
        while(left <= right) {
            int mid = (right + left) >> 1;
            if(nums[mid] > nums[0]) { // nums[mid] > nums[0] 说明mid在有序一侧,要往无序一侧寻找
                left = mid + 1;
            } else { // mid在无序一侧,最小值一定在mid左边
                res = mid;
                right = mid - 1;
            }
        }
        return nums[res];
    }
};

68、寻找两个正序数组的中位数

思路

两个数组共m + n 个数,寻找合并起来的中位数,代表在数组1中找到一个位置i,数组2中找到一个位置j,使得A[i] < B[j + 1] && B[j] < A[i + 1],其中,i, j 满足关系i + j = (m + n + 1) / 2

但是关于数组越界等细节比较繁琐,需要仔细想想。

code
class Solution {
public:
    double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
        if (nums1.size() > nums2.size()) {
            return findMedianSortedArrays(nums2, nums1);
        }
        
        int m = nums1.size();
        int n = nums2.size();
        int left = 0, right = m;
        // median1:前一部分的最大值
        // median2:后一部分的最小值
        int median1 = 0, median2 = 0;

        while (left <= right) {
            // 前一部分包含 nums1[0 .. i-1] 和 nums2[0 .. j-1]
            // 后一部分包含 nums1[i .. m-1] 和 nums2[j .. n-1]
            int i = (left + right) / 2;
            int j = (m + n + 1) / 2 - i;

            // nums_im1, nums_i, nums_jm1, nums_j 分别表示 nums1[i-1], nums1[i], nums2[j-1], nums2[j]
            int nums_im1 = (i == 0 ? INT_MIN : nums1[i - 1]);
            int nums_i = (i == m ? INT_MAX : nums1[i]);
            int nums_jm1 = (j == 0 ? INT_MIN : nums2[j - 1]);
            int nums_j = (j == n ? INT_MAX : nums2[j]);

            if (nums_im1 <= nums_j) {
                median1 = max(nums_im1, nums_jm1);
                median2 = min(nums_i, nums_j);
                left = i + 1;
            } else {
                right = i - 1;
            }
        }

        return (m + n) % 2 == 0 ? (median1 + median2) / 2.0 : median1;
    }
};

作者:力扣官方题解
链接:https://leetcode.cn/problems/median-of-two-sorted-arrays/solutions/258842/xun-zhao-liang-ge-you-xu-shu-zu-de-zhong-wei-s-114/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

69、有效的括号

思路

左括号入栈,右括号判断与栈顶是否匹配,匹配则出栈,不匹配则失败。

code
class Solution {
public:
    bool isValid(string s) {
        char stack[10001];
        int top =0;
        for (int i = 0; s[i]; ++i) {
            if (s[i] == '(' || s[i] == '[' || s[i] == '{') stack[top++] = s[i];
            else {
                if ((--top) < 0)                      return false;//先减减,让top指向栈顶元素
                if (s[i] == ')' && stack[top] != '(') return false;
                if (s[i] == ']' && stack[top] != '[') return false;
                if (s[i] == '}' && stack[top] != '{') return false;
            }
        }
        return (!top);//防止“【”这种类似情况
    }
};

70、最小栈

思路

一个栈存已有的数据,一个栈存目前的最小值

code
class MinStack {
public:
    stack<int> stack1, stack2;
    MinStack() {

    }
    
    void push(int val) {

        stack2.push(stack1.empty() ? val : min(stack2.top(), val));
        stack1.push(val);
    }
    
    void pop() {
        stack1.pop();
        stack2.pop();
    }
    
    int top() {
        return stack1.top();
    }
    
    int getMin() {
        return stack2.top();
    }
};

/**
 * Your MinStack object will be instantiated and called as such:
 * MinStack* obj = new MinStack();
 * obj->push(val);
 * obj->pop();
 * int param_3 = obj->top();
 * int param_4 = obj->getMin();
 */

71、字符串解码

思路

栈保存一个二元组(int count, string str),遇到数字则计算count,遇到左括号则取str入栈,遇到右括号则出栈

code
class Solution {
public:
    string decodeString(string s) {
        stack<pair<string,int>> st;
        string res="";
        int mul=0;
        for (auto c:s){
            if(c=='['){
                st.emplace(res,mul);
                res="";
                mul=0;
            }else if(c==']'){
                auto [last_res,cur_mul]=st.top();
                st.pop();
                string tmp=last_res;
                for(int i=0;i<cur_mul;i++) tmp+=res;
                res=tmp;
            }else if('0'<=c && c<='9'){
                mul=mul*10+(c-'0');
            }else{
                res += c;
            }
        }
        return res;
    }
};

72、每日温度

思路

单调栈

code
class Solution {
public:
    vector<int> dailyTemperatures(vector<int>& temperatures) {
        int n = temperatures.size();
        vector<int> ans(n);
        stack<int> s;
        for (int i = 0; i < n; ++i) {
            while (!s.empty() && temperatures[i] > temperatures[s.top()]) {
                int previousIndex = s.top();
                ans[previousIndex] = i - previousIndex;
                s.pop();
            }
            s.push(i);
        }
        return ans;
    }
};

作者:力扣官方题解
链接:https://leetcode.cn/problems/daily-temperatures/solutions/283196/mei-ri-wen-du-by-leetcode-solution/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

73、柱状图中最大的矩形

思路
  1. 对于一个高度,如果能得到向左和向右的边界
  2. 那么就能对每个高度求一次面积
  3. 遍历所有高度,即可得出最大面积
  4. 使用单调栈,在出栈操作时得到前后边界并计算面积
code
class Solution {
public:
    int largestRectangleArea(vector<int>& heights)
    {
        int ans = 0;
        vector<int> st;
        heights.insert(heights.begin(), 0);
        heights.push_back(0);
        for (int i = 0; i < heights.size(); i++)
        {
            while (!st.empty() && heights[st.back()] > heights[i])
            {
                int cur = st.back();
                st.pop_back();
                int left = st.back() + 1;
                int right = i - 1;
                ans = max(ans, (right - left + 1) * heights[cur]);
            }
            st.push_back(i);
        }
        return ans;
    }
};

74、数组中的第k个最大元素

思路
  1. 大根堆
  2. 基于快排的选择方法
code
class Solution {
public:
    int quickselect(vector<int> &nums, int l, int r, int k) {
        if(l == r) return nums[k];
        int partition = nums[l], i = l - 1, j = r + 1;

        while(i < j) {
            do i++; while(nums[i] < partition);
            do j--; while(nums[j] > partition);
            if(i < j) swap(nums[i], nums[j]);
        }
        if(k <= j) return quickselect(nums, l, j, k);
        else return quickselect(nums, j + 1, r, k);
    }

    int findKthLargest(vector<int> &nums, int k) {
        int n = nums.size();
        return quickselect(nums, 0, n - 1, n - k);
    }
};

75、前K个高频元素

思路

哈希表 + 小根堆

code
class Solution {
public:
    static bool cmp(pair<int, int>& m, pair<int, int>& n) {
        return m.second > n.second;
    }

    vector<int> topKFrequent(vector<int>& nums, int k) {
        unordered_map<int, int> mp;
        priority_queue<pair<int, int>, vector<pair<int, int>>, decltype(&cmp)> pq(cmp); // 优先队列自定义排序的写法
        vector<int> res;
        for(int num : nums) {
            mp[num]++;
        }
        for(auto& [num, count] : mp) {
            if(pq.size() == k) {
                if(pq.top().second < count) {
                    pq.pop();
                    pq.emplace(num, count);
                }
            } else {
                pq.emplace(num, count);
            }
        }

        while(k--) {
            res.push_back(pq.top().first);
            pq.pop();
        }
        return res;
    }
};

76、数据流的中位数

思路

一个大根堆保存较小的一半元素,一个小根堆保存较大的一半元素,两个堆顶即为中位数。

code
class MedianFinder {
public:
    priority_queue<int, vector<int>, less<int>> queMin;
    priority_queue<int, vector<int>, greater<int>> queMax;

    MedianFinder() {}

    void addNum(int num) {
        if (queMin.empty() || num <= queMin.top()) {
            queMin.push(num);
            if (queMax.size() + 1 < queMin.size()) {
                queMax.push(queMin.top());
                queMin.pop();
            }
        } else {
            queMax.push(num);
            if (queMax.size() > queMin.size()) {
                queMin.push(queMax.top());
                queMax.pop();
            }
        }
    }

    double findMedian() {
        if (queMin.size() > queMax.size()) {
            return queMin.top();
        }
        return (queMin.top() + queMax.top()) / 2.0;
    }
};

77、买卖股票的最佳时机

思路

维护一个整数当前可获得的最大利润,维护一个正整数表示当前见过的最小值,遍历即可

code
class Solution {
public:
    int maxProfit(vector<int>& prices) {
        int res = 0, min_price = prices[0];
        for(int i = 1; i < prices.size(); ++i) {
            if(prices[i] - min_price > res) res = prices[i] - min_price;
            if(prices[i] < min_price) min_price = prices[i];
        }
        return res;
    }
};

78、跳跃游戏

思路

维护当前能跳到的最远位置

code
class Solution {
public:
    bool canJump(vector<int>& nums) {
        int end = nums[0];
        for(int i = 1; i < nums.size(); ++i) {
            if(i > end) return false;
            end = max(end, i + nums[i]);
        }
        return true;
    }
};

79、跳跃游戏II

思路

维护当前能跳到的最远位置,和上次的最远位置(边界),当到达边界时,更新边界为当前能到达的最远位置,跳跃次数+1

code
class Solution {
public:
    int jump(vector<int>& nums) {
        int maxPos = 0, n = nums.size(), end = 0, step = 0;
        for (int i = 0; i < n - 1; ++i) {
            if (maxPos >= i) {
                maxPos = max(maxPos, i + nums[i]);
                if (i == end) {
                    end = maxPos;
                    ++step;
                }
            }
        }
        return step;
    }
};

作者:力扣官方题解
链接:https://leetcode.cn/problems/jump-game-ii/solutions/230241/tiao-yue-you-xi-ii-by-leetcode-solution/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

80、划分字母区间

思路

记录每个字母最后一次出现的位置,遍历字符串更新当前区间右边界,抵达时划分区间

code
class Solution {
public:
    vector<int> partitionLabels(string s) {
        int last[26];
        int length = s.size();
        for (int i = 0; i < length; i++) {
            last[s[i] - 'a'] = i;
        }
        vector<int> partition;
        int start = 0, end = 0;
        for (int i = 0; i < length; i++) {
            end = max(end, last[s[i] - 'a']);
            if (i == end) {
                partition.push_back(end - start + 1);
                start = end + 1;
            }
        }
        return partition;
    }
};

作者:力扣官方题解
链接:https://leetcode.cn/problems/partition-labels/solutions/455703/hua-fen-zi-mu-qu-jian-by-leetcode-solution/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

81、爬楼梯

思路
  1. 动规

d p [ i ] = d p [ i − 1 ] + d p [ i − 2 ] ; dp[i] = dp[i - 1] + dp[i - 2]; dp[i]=dp[i1]+dp[i2];

  1. 矩阵快速幂。妙不可言
code

递推的代码没什么好说的,这是矩阵快速幂的代码。

class Solution {
public:
    vector<vector<long long>> multiply(vector<vector<long long>> &a, vector<vector<long long>> &b) {
        vector<vector<long long>> c(2, vector<long long>(2));
        for (int i = 0; i < 2; i++) {
            for (int j = 0; j < 2; j++) {
                c[i][j] = a[i][0] * b[0][j] + a[i][1] * b[1][j];
            }
        }
        return c;
    }

    vector<vector<long long>> matrixPow(vector<vector<long long>> a, int n) {
        vector<vector<long long>> ret = {{1, 0}, {0, 1}};
        while (n > 0) {
            if ((n & 1) == 1) {
                ret = multiply(ret, a);
            }
            n >>= 1;
            a = multiply(a, a);
        }
        return ret;
    }

    int climbStairs(int n) {
        vector<vector<long long>> ret = {{1, 1}, {1, 0}};
        vector<vector<long long>> res = matrixPow(ret, n);
        return res[0][0];
    }
};

作者:力扣官方题解
链接:https://leetcode.cn/problems/climbing-stairs/solutions/286022/pa-lou-ti-by-leetcode-solution/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

82、杨辉三角

思路

模拟

code
class Solution {
public:
    vector<vector<int>> generate(int numRows) {
        vector<vector<int>> ret(numRows);
        for (int i = 0; i < numRows; ++i) {
            ret[i].resize(i + 1);
            ret[i][0] = ret[i][i] = 1;
            for (int j = 1; j < i; ++j) {
                ret[i][j] = ret[i - 1][j] + ret[i - 1][j - 1];
            }
        }
        return ret;
    }
};

83、打家劫舍

思路

d p [ i ] = m a x ( d p [ i − 1 ] , d p [ i − 2 ] + n u m s [ i ] ) dp[i] = max(dp[i - 1], dp[i - 2] + nums[i]) dp[i]=max(dp[i1],dp[i2]+nums[i])

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

84、完全平方数

思路

动态规划
f [ i ] = 1 + m i n j = 1 i f [ i − j 2 ] f[i] = 1 + min _{j = 1} ^{\sqrt{i}} f[i - j^2] f[i]=1+minj=1i f[ij2]

code
class Solution {
public:
    int numSquares(int n) {
        vector<int> f(n + 1);
        for (int i = 1; i <= n; i++) {
            int minn = INT_MAX;
            for (int j = 1; j * j <= i; j++) {
                minn = min(minn, f[i - j * j]);
            }
            f[i] = minn + 1;
        }
        return f[n];
    }
};

作者:力扣官方题解
链接:https://leetcode.cn/problems/perfect-squares/solutions/822940/wan-quan-ping-fang-shu-by-leetcode-solut-t99c/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

85、零钱兑换

思路

动态规划
f [ i ] = m i n j = 0 n − 1   f ( i − c j ) + 1 f[i] = min _{j=0} ^{n-1} \ f(i - c_j) + 1 f[i]=minj=0n1 f(icj)+1

code
class Solution {
public:
    int coinChange(vector<int>& coins, int amount) {
        int Max = amount + 1;
        vector<int> dp(amount + 1, Max);
        dp[0] = 0;
        for (int i = 1; i <= amount; ++i) {
            for (int j = 0; j < (int)coins.size(); ++j) {
                if (coins[j] <= i) {
                    dp[i] = min(dp[i], dp[i - coins[j]] + 1);
                }
            }
        }
        return dp[amount] > amount ? -1 : dp[amount];
    }
};

作者:力扣官方题解
链接:https://leetcode.cn/problems/coin-change/solutions/132979/322-ling-qian-dui-huan-by-leetcode-solution/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

96、只出现一次的数字

思路

异或运算

code
class Solution {
public:
    int singleNumber(vector<int>& nums) {
        int ret = 0;
        for (auto e: nums) ret ^= e;
        return ret;
    }
};

97、多数元素

思路

摩尔投票法:随变立一个候选者,遍历遇到和它相等的则票数加一,不等则票数减一,若减为负数,改立新的候选者。

code
class Solution {
public:
    int majorityElement(vector<int>& nums) {
        int candidate = nums[0];
        int cnt = 1;
        for(int i = 1; i < nums.size(); ++i) {
            if(nums[i] == candidate) cnt++;
            else {
                cnt--;
                if(cnt < 0) {
                    candidate = nums[i];
                    cnt = 1;
                }
            }
        }
        return candidate;
    }
};

98、颜色分类

思路

双指针一个指向下一个0应该在的位置,一个指向下一个2应该在的位置,从两头向中间移动。

i0向后移动,遇到0就和第一个指针交换,遇到2就和第二个交换,遇到1就不理会。

code
class Solution {
public:
    void sortColors(vector<int>& nums) {
        int n = nums.size();
        int p0 = 0, p2 = n - 1;
        for (int i = 0; i <= p2; ++i) {
            while (i <= p2 && nums[i] == 2) {
                swap(nums[i], nums[p2]);
                --p2;
            }
            if (nums[i] == 0) {
                swap(nums[i], nums[p0]);
                ++p0;
            }
        }
    }
};

99、下一个排列

思路
  1. 在 尽可能靠右的低位 进行交换,需要 从后向前 查找
  2. 将一个 尽可能小的「大数」 与前面的「小数」交换。比如 123465,下一个排列应该把 5 和 4 交换而不是把 6 和 4 交换
  3. 将「大数」换到前面后,需要将「大数」后面的所有数 重置为升序,升序排列就是最小的排列。
code
class Solution {
public:
    void nextPermutation(vector<int>& nums) {
        next_permutation(nums.begin(), nums.end());
    }
};

不要学,还是老老实实看题解吧!

100、寻找重复数

思路

把该序列看做一个图,nums[i] 表示 从 inums[i]有一条边。由于nums.length == n + 11 <= nums[i] <= n,所以如果没有重复数,该图应该是一个链表,有重复数,该图应该是一个有环的链表。

题目变为:返回链表第一个入环节点

参考 26.环形链表II。妙不可言!

code
class Solution {
public:
    int findDuplicate(vector<int>& nums) {
        int slow = 0, fast = 0;
        do {
            slow = nums[slow];
            fast = nums[nums[fast]];
        } while (slow != fast); // 相遇
        slow = 0; // 一个指针回到首部重新走
        while (slow != fast) {
            slow = nums[slow];
            fast = nums[fast];
        }
        return slow;
    }
};

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值