leetcode hot 100(2)

leetcode hot 100(2)

图论

回溯

二分查找

class Solution {
public:
    int getKth(vector<int>& nums1, int start1, int end1, vector<int>& nums2, int start2, int end2, int k) {
        int len1 = end1 - start1 + 1;
        int len2 = end2 - start2 + 1;
        // 当nums1为空,则直接在nums2返回第k个数
        if (len1 == 0) return nums2[start2 + k - 1];
        // 当nums2为空,则直接在nums1返回第k个数
        if (len2 == 0) return nums1[start1 + k - 1];
        // 当k=1,不用再继续查找,直接返回nums1[start1], nums2[start2]之中更小的值
        if (k == 1) return min(nums1[start1], nums2[start2]);

        // 找到num1和nums2中第k/2(向下取整)个数,若不够k/2个数,直接取最后一位
        int i = start1 + min(len1, k / 2) - 1;
        int j = start2 + min(len2, k / 2) - 1;

        if (nums1[i] > nums2[j]) { // nums2[start2, j] 可以排除
            return getKth(nums1, start1, end1, nums2, j + 1, end2, k - (j - start2 + 1));
        } else { // nums1[start1, i] 可以排除
            return getKth(nums1, i + 1, end1, nums2, start2, end2, k - (i - start1 + 1));
        }
    }
    double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
        int n = nums1.size();
        int m = nums2.size();
        int k = (n + m) / 2;
        if ((n + m) & 1) { // 中位数只有一个
            return getKth(nums1, 0, n - 1, nums2, 0, m - 1, k + 1);
        } else { // 中位数只有两个
            return (getKth(nums1, 0, n - 1, nums2, 0, m - 1, k) + getKth(nums1, 0, n - 1, nums2, 0, m - 1, k + 1)) * 0.5;
        }
    }
};

一个重要的结论:

对于每条柱heights[i], 以他为矩形的长,最大面积是 heights[i] = (right_i - left_i + 1)。

right_i是从i开始,往右找,第一个heights[right_i] < heights[i]
left_i是从i开始,往左找,第一个heights[left_i] < heights[i]

答案肯定是以某个height[i]为高的矩形,遍历所有的可能即可。

根据这个结论,可以利用单调增栈的方式,找出每个i的left_i和right_i。

为了让第一个能找到left_i和最后一个元素能找到right_i,在数组前后各添加一个0.

单调增栈出入栈。一个元素出栈时就能找到left_i和right_i,此时计算面积即可。

  • 每日温度: 单调递减栈
  • 最小栈:两个栈模拟,一个正常放,一个最小栈min。每次入栈,先比较栈顶,比栈顶小,入栈。出栈一样,跟栈顶比较,相等,出栈。
  • 有效的括号:略
  • 394. 字符串解码 - 力扣(LeetCode) : 对于字符串里的字符分四种情况
    • 数字:取出数字,更新下标,将数字入栈
    • 字母:取出字符串,更新下标,将字符串入栈
    • [ :直接入栈
    • ] :一直出栈,直至遇到栈顶为[ , 将出栈的字符串组成str 。 把 [ 和 前面的数字也出栈num,将str重复num遍,再把字符串入栈。重复以上步骤,直至遇到字符串结尾。在把栈中字符串组装起来。
class Solution {
public:
    string getNum(const string& s, int& index) { // 从index起获取一个数字
        string numstr;
        while (index < s.size() && isdigit(s[index])) {
            numstr.push_back(s[index]);
            ++index;
        }
        return numstr; 
    }

    string getStr(const string& s, int& index) {
        string str;
        while (index < s.size() && islower(s[index])) { // 从index起获取一个字符串
            str.push_back(s[index]);
            ++index;
        }
        return str;
    }

    string decodeString(string s) {
        stack<string> st;
        int index = 0;
        while (index < s.size()) {
            if (isdigit(s[index])) {
                st.push(getNum(s, index));
            } else if (islower(s[index])) {
                st.push(getStr(s, index));
            } else {
                if (s[index] == '[') {
                    st.push("[");
                } else { // ']'
                    string tmp;
                    while (!st.empty() && st.top() != "[") { // 一定是whie,[]之间可能有多个[],从而有多个字符串,所以需要将这些字符串拼接起来
                        tmp = st.top() + tmp;
                        st.pop();
                    }
                    
                    st.pop(); // [
         
                    int times = stoi(st.top());
                    st.pop(); // num
                    
                    string shouldPush;
                    while (times--) {
                        shouldPush += tmp;
                    }
                    st.push(shouldPush);
                }
                ++index;
            }
        }
        string res;
        while (!st.empty()) {
            res = st.top() + res;
            st.pop();
        }
        return res;
    }
};

  • 数据流的中位数:用两个堆来完成,一个大根堆,一个小根堆。1、大根堆放数据流的较小半部分,小根堆放数据流的较大的半部分。2、大根堆元素个数始终最多比小根堆元素个数多一。插入元素时主要要维持这两个性质。

  • 前 K 个高频元素:map统计数字出现的频次,然后维护大小为k的小根堆。

  • 数组中的第K个最大元素:维护大小为k的小根堆 或者 基于快排

贪心算法

动态规划

  • 打家劫舍:dp[i] = max(dp[i-1], dp[i - 2] + nums[i])

  • 完全平方数: 完全背包,物品是各个完全平方数,而背包大小是目标和。用最少的物品装满背包,物品可以重复利用。用一维的话,背包从小到大就是完全背包.从大到小,就是01背包。dp[j] 就是j的答案。

  • 零钱兑换:同上,一样是完全背包,一样是求物品数最少

  • 单词拆分:dp[j]代表s[0~j]可以划分,每到一个位置对所有单词遍历,看看是否能划分

多维动态规划

  • 不同路径: dp[i] [j] = dp[i-1] [j] + dp[i] [j-1]
  • 最小路径和: grid[i] [j] += min(grid[i-1] [j], gridi)
  • 最长回文子串: 对每个字符和作为中心,从两边展开获取回文串。n^2。(回文串长度既可以是单数也可以是双数)
  • 最长公共子序列: dp[i] [j] = (t1[i] == t2[j]) ? dp[i-1] [j-1] + 1 : min(dp[i-1] [j], dp[i] [j - 1])
  • 编辑距离: 要知道删除和增加和修改都是等价的. word1[i] == word2[j] , dp[i] [j] = dp[i-1] [j-1], 否则,dp[i] [j] = min(dp[i-1] [j-1], dp[i-1] [j], dp[i] [j]) + 1,注意不要漏了dp[i-1] [j-1]

技巧

class Solution {
public:
    void sortColors(vector<int>& nums) {
        int p0 = 0, p1 = 0;
        for (int i = 0; i < nums.size(); ++i) {
            if (nums[i] == 0) {
                swap(nums[i], nums[p0]);
                if (p0 < p1) {              // 如果p0 < p1,证明nums[p0]=1
                    swap(nums[i], nums[p1]);// 将1换回来
                }
                ++p0;   // 因为是0,p0和p1都需要更新
                ++p1;
            } else if (nums[i] == 1) {
                swap(nums[i], nums[p1]);
                ++p1;
            }
        }
    }
};
class Solution {
public:
    void nextPermutation(vector<int>& nums) {
        int len = nums.size();
        if (len < 2) return ;

        int i = len - 2; 
        while (i >= 0 && nums[i] >= nums[i + 1]) {
            --i;
        }
        
        // 此时nums[i] < nums[i+1],代表nums[i+1, n)中必有数nums[k]大于nums[i]
        // 尽量从后面找nums[k], 此时
        int k = len - 1;
        if (i >= 0) {
            while (nums[i] >= nums[k]) {
                --k;
            }
            swap(nums[i], nums[k]);
        }

        reverse(nums.begin()+i+1, nums.end());
    }
};

123456
123465
123546

654321
可以看到有这样的关系:123456 < 123465 < 123546 < … < 654321。

算法推导
如何得到这样的排列顺序?这是本文的重点。我们可以这样来分析:

我们希望下一个数 比当前数大,这样才满足 “下一个排列” 的定义。因此只需要 将后面的「大数」与前面的「小数」交换,就能得到一个更大的数。比如 123456,将 5 和 6 交换就能得到一个更大的数 123465。
我们还希望下一个数增加的幅度尽可能的小,这样才满足“下一个排列与当前排列紧邻“的要求。为了满足这个要求,我们需要:在尽可能靠右的低位 进行交换,需要从后向前查找将一个尽可能小的「大数」 与前面的「小数」交换。比如 123465,下一个排列应该把 5 和 4 交换而不是把 6 和 4 交换将「大数」换到前面后,需要将「大数」后面的所有数 重置为升序,升序排列就是最小的排列。以 123465 为例:首先按照上一步,交换 5 和 4,得到 123564;然后需要将 5 之后的数重置为升序,得到 123546。显然 123546 比 123564 更小,123546 就是 123465 的下一个排列
以上就是求 “下一个排列” 的分析过程。

算法过程
标准的 “下一个排列” 算法可以描述为:

从后向前 查找第一个 相邻升序 的元素对 (i,j),满足 A[i] < A[j]。此时 [j,end) 必然是降序
在 [j,end) 从后向前 查找第一个满足 A[i] < A[k] 的 k。A[i]、A[k] 分别就是上文所说的「小数」、「大数」
将 A[i] 与 A[k] 交换
可以断定这时 [j,end) 必然是降序,逆置 [j,end),使其升序
如果在步骤 1 找不到符合的相邻元素对,说明当前 [begin,end) 为一个降序顺序,则直接跳到步骤 4
该方法支持数据重复,且在 C++ STL 中被采用。

  • 寻找重复数: 数组元素与下标组成有环链表,变成找出环的入口。变成快慢指针找有环链表环的入口。
 0 1 2 3 4
[3,1,3,4,2]
0->3->4->2->3
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值