文章

Top100(下)

LeetCode Top 100 是精选的高频面试题单,涵盖经典算法和数据结构问题,帮助求职者高效准备技术面试,提升算法能力。

Top100(下)

Top100(下)

20. 有效的括号

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
bool isValid(char *s) {
    int len = strlen(s);
    if (len % 2 == 1) return false;

    // 符号栈
    char *stack = (char *) malloc(sizeof(char) * (len + 1));
    int top = 0;
    for (int i = 0; i < len; ++i) {
        char cur = s[i];
        // 栈空直接插入
        if (top == 0) {
            if (cur == ')' || cur == ']' || cur == '}') return false;
            stack[top++] = cur;
            continue;
        }

        if ((cur == ')' && stack[top - 1] == '(')
            || (cur == ']' && stack[top - 1] == '[')
            || (cur == '}' && stack[top - 1] == '{')) {
            // 与栈顶抵消
            top--;
        } else {
            // 抵消不了,就入栈
            stack[top++] = cur;
        }
    }
    return top == 0;
}

155. 最小栈

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
// 使用辅助栈记录当前最小值
typedef struct {
    // 数据栈
    int *stack;
    int top;
    int maxSize;
    // 最小栈,保存当前位置时,数据栈中最小元素
    int *minStack;
} MinStack;

MinStack *minStackCreate() {
    MinStack *s = (MinStack *) malloc(sizeof(MinStack));
    s->stack = (int *) malloc(sizeof(int) * 10);
    s->minStack = (int *) malloc(sizeof(int) * 10);
    s->top = 0;
    s->maxSize = 10;
    return s;
}

void minStackPush(MinStack *obj, int x) {
    // 动态扩容到1.75倍
    if (obj->top == obj->maxSize) {
        int newSize = (obj->maxSize << 1) - (obj->maxSize >> 2);
        // realloc重新调整内存大小
        obj->stack = (int *) realloc(obj->stack, sizeof(int) * newSize);
        obj->minStack = (int *) realloc(obj->minStack, sizeof(int) * newSize);
        obj->maxSize = newSize;
    }

    // 同步修改最小栈,栈顶保存当前数据栈中最小元素
    if (obj->top == 0 || x < obj->minStack[obj->top - 1])
        // 栈空或者新元素比上个最小元素还小
        obj->minStack[obj->top] = x;
    else
        // 否则使用上个最小元素
        obj->minStack[obj->top] = obj->minStack[obj->top - 1];

    // 把数据压入数据栈
    obj->stack[obj->top++] = x;
}

void minStackPop(MinStack *obj) {
    obj->top--;
}

int minStackTop(MinStack *obj) {
    return obj->stack[obj->top - 1];
}

int minStackGetMin(MinStack *obj) {
    return obj->minStack[obj->top - 1];
}

void minStackFree(MinStack *obj) {
    free(obj);
    obj = NULL;
}

394. 字符串解码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
class Solution {
public:
    string decodeString(string s) {
        stack<char> stk;
        deque<char> deq;

        for (int i = 0; i < s.size(); ++i) {
            while (i < s.size() && s[i] != ']') {
                stk.emplace(s[i]);
                i++;
            }

            // 已经到结尾了
            if (s[i] != ']') break;

            // 记录括号内的字符串
            string tempStr;
            while (stk.top() != '[') {
                deq.emplace_front(stk.top());
                stk.pop();
            }
            // 弹出 '['
            stk.pop();
            while (!deq.empty()) {
                tempStr += deq.front();
                deq.pop_front();
            }

            // 记录重复次数
            int count = 0;
            while (!stk.empty() && stk.top() >= '0' && stk.top() <= '9') {
                deq.emplace_front(stk.top());
                stk.pop();
            }
            while (!deq.empty()) {
                count *= 10;
                count += deq.front() - '0';
                deq.pop_front();
            }

            // 记录重复 count 次的字符串
            string repeated;
            for (int j = 0; j < count; ++j)
                repeated.append(tempStr);

            // 重新入栈
            for (auto &c: repeated)
                stk.emplace(c);
        }

        // 全部出栈
        string res;
        deq.clear();
        while (!stk.empty()) {
            deq.emplace_front(stk.top());
            stk.pop();
        }
        while (!deq.empty()) {
            res += deq.front();
            deq.pop_front();
        }

        return res;
    }
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
class Solution {
public:
    // "abc2[a2[c]t]2[k]xyz";
    // abcacctacctkkxyz
    string decodeString(const string &s) {
        // 临时记录数字和数字前的字符串
        int multi = 0;
        string res;
        stack<int> stack_multi;
        stack<string> stack_res;

        for (char c: s) {
            if (c == '[') {
                // 遇到左括号,说明左括号前的数字已经被确定,存入栈中
                stack_multi.emplace(multi);
                // 数字之前的字符串也确定了,存入栈中
                stack_res.emplace(res);
                // 清空这两个临时变量
                multi = 0;
                res.clear();
            } else if (c == ']') {
                // 取出当前括号内字符串应该重复的次数
                int cur_multi = stack_multi.top();
                stack_multi.pop();
                // 重复对应的次数后记录到tmp中
                string tmp;
                for (int i = 0; i < cur_multi; i++) tmp += res;
                // 再接到之前数字前已经出现的字符串后面
                res = stack_res.top() + tmp;
                stack_res.pop();
            } else if (c >= '0' && c <= '9') {
                // 确定重复次数
                multi = multi * 10 + (c - '0');
            } else {
                // 记录数字前的字符串,或者是追加右括号后面的字符串
                res.push_back(c);
            }
        }
        return res;
    }
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
class Solution {
public:
    // 当前处理位置的下标
    int index = 0;

    // 递归
    string decodeString(string s) {
        string res;
        int multi = 0;
        while (index < s.size()) {
            if (s[index] >= '0' && s[index] <= '9') {
                // 计算重复次数
                multi = 10 * multi + s[index] - '0';
            } else if (s[index] == '[') {
                // 递归处理括号内的字符串
                index++;
                string temp = decodeString(s);
                // 重复 multi 次
                while (multi > 0) {
                    res += temp;
                    multi--;
                }
            } else if (s[index] == ']') {
                // 结束递归,返回括号内的字符串
                break;
            } else {
                res += s[index];
            }
            index++;
        }
        return res;
    }
};

739. 每日温度

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Solution {
public:
    vector<int> dailyTemperatures(vector<int> &temperatures) {
        vector<int> res(temperatures.size(), 0);
        // 单调递减栈,存下标
        stack<int> stk;

        for (int i = 0; i < temperatures.size(); ++i) {
            // 把栈顶比当前元素小的全都弹出
            while (!stk.empty() && temperatures[stk.top()] < temperatures[i]) {
                int index = stk.top();
                stk.pop();
                // 记录下标差
                res[index] = i - index;
            }
            // 直接入栈,栈底到栈顶递减
            stk.emplace(i);
        }
        return res;
    }
};

84. 柱状图中最大的矩形

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#include <iostream>
#include <vector>
#include <map>
#include <stack>

using namespace std;

class Solution {
public:
    int largestRectangleArea(vector<int> &heights) {
        int len = heights.size();
        int res = 0;
        // 栈底到栈顶递增
        stack<int> stk;

        // 以当前位置的高度为矩形的高度,向两侧扩展,直到两侧第一个更低的位置,构成矩形的宽
        for (int i = 0; i < len; ++i) {
            while (!stk.empty() && (heights[stk.top()] >= heights[i])) {
                int popIndex = stk.top();
                stk.pop();
                int left = stk.empty() ? -1 : stk.top();
                int width = i - left - 1;
                res = max(res, heights[popIndex] * width);
            }
            stk.emplace(i);
        }

        while (!stk.empty()) {
            int popIndex = stk.top();
            stk.pop();
            int left = stk.empty() ? -1 : stk.top();
            int width = len - left - 1;
            res = max(res, heights[popIndex] * width);
        }

        return res;
    }
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
class Solution {
public:
    int largestRectangleArea(vector<int> &heights) {
        int result = 0;
        // 单调递增栈,存放下标
        stack<int> stk;
        // 数组头部加入元素0
        heights.insert(begin(heights), 0);
        // 数组尾部加入元素0
        heights.push_back(0);

        for (int i = 0; i < heights.size(); i++) {
            // 依次弹出大于当前元素的栈顶元素
            // 等于当前元素的栈顶右边界是暂时无法确定的
            // 因为它可以经过当前元素继续向右边扩展,直到遇到第一个严格小于它的元素,所有先不用出栈
            while (!stk.empty() && heights[i] < heights[stk.top()]) {
                int mid = stk.top();
                stk.pop();
                // 每弹出一个栈顶,都计算一下区间 [弹出的栈顶, 当前元素) 内能形成的最大矩形面积
                int w = i - stk.top() - 1;
                int h = heights[mid];
                result = max(result, w * h);
            }
            stk.emplace(i);
        }
        return result;
    }
};

215. 数组中的第K个最大元素

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
// 用整个数组来建立大顶堆,然后从中取k次堆顶,第k次取出的就是结果
class Solution {
public:
    // 自顶向下调整堆,len 是当前堆的大小,复杂度 O(logn)
    void adjustHeap(vector<int> &array, int currentIndex, int len) {
        // 要调整位置的元素
        int temp = array[currentIndex];
        // 左孩子下标
        int leftChildIndex = 2 * currentIndex + 1;

        // 自顶向下,调整到最后一个节点
        while (leftChildIndex <= (len - 1)) {
            // 把左右孩子中较大者的下标赋给 leftChildIndex
            if (leftChildIndex < (len - 1)
                && (array[leftChildIndex] < array[leftChildIndex + 1]))
                leftChildIndex++;
            // 和当前元素比较大小,决定要不要调整堆
            // 1. 不需要调整
            if (array[leftChildIndex] <= temp) break;
            // 2. 需要调整
            array[currentIndex] = array[leftChildIndex];
            currentIndex = leftChildIndex;
            leftChildIndex = 2 * currentIndex + 1;
        }
        // 放在最终的位置
        array[currentIndex] = temp;
    }

    int findKthLargest(vector<int> &nums, int k) {
        // 建堆:从最后一个非叶子节点开始向上调整每个节点,复杂度 O(n)
        // 最后一个非叶子节点下标为 n/2-1,下标从0开始
        for (int i = nums.size() / 2 - 1; i >= 0; i--) {
            adjustHeap(nums, i, nums.size());
        }
        // 每次把大顶堆的堆顶移到末尾,并且堆的大小减一,最终形成升序列表
        for (int len = nums.size() - 1, count = k; count > 0; len--, count--) {
            swap(nums[0], nums[len]);
            adjustHeap(nums, 0, len);
        }
        return nums[nums.size() - k];
    }
};
1
2
3
4
5
6
7
8
9
10
11
12
// 用整个数组来建立大顶堆,然后从中取k次堆顶,第k次取出的就是结果
class Solution {
public:
    int findKthLargest(vector<int> &nums, int k) {
        priority_queue<int> heap{begin(nums), end(nums)};
        while (k > 1) {
            heap.pop();
            k--;
        }
        return heap.top();
    }
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
// 用前 k 个元素建立小顶堆,后续的元素于堆顶比较,更大的话就加入堆
class Solution {
public:
    // 小顶堆的调整
    void adjustHeap(vector<int> &nums, int currentIndex, int len) {
        int temp = nums[currentIndex];
        int leftChildIndex = 2 * currentIndex + 1;
        while (leftChildIndex <= len - 1) {
            if (leftChildIndex < len - 1
                && (nums[leftChildIndex] > nums[leftChildIndex + 1]))
                leftChildIndex++;
            if (nums[leftChildIndex] >= temp)break;
            nums[currentIndex] = nums[leftChildIndex];
            currentIndex = leftChildIndex;
            leftChildIndex = 2 * currentIndex + 1;
        }
        nums[currentIndex] = temp;
    }


    int findKthLargest(vector<int> &nums, int k) {
        // 用前 k 个元素建立小顶堆
        for (int i = k / 2 - 1; i >= 0; i--) {
            adjustHeap(nums, i, k);
        }
        for (int i = k; i < nums.size(); ++i) {
            if (nums[0] < nums[i]) {
                // 比堆顶大就替换堆顶,然后调整小顶堆
                nums[0] = nums[i];
                adjustHeap(nums, 0, k);
            }
        }
        // 最终包含 k 个元素的小顶堆堆顶就是第 k 个大的元素
        return nums[0];
    }
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 用前 k 个元素建立小顶堆,后续的元素于堆顶比较,更大的话就加入堆
class Solution {
public:
    int findKthLargest(vector<int> &nums, int k) {
        // 小顶堆
        priority_queue<int, vector<int>, greater<>> heap{begin(nums), begin(nums) + k};
        for (int i = k; i < nums.size(); ++i) {
            if (nums[i] > heap.top()) {
                heap.pop();
                heap.emplace(nums[i]);
            }
        }
        return heap.top();
    }
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
class Solution {
public:
    int res;
    int target;

    // 快速选择
    void quickSelect(vector<int> &nums, int left, int right) {
        if (left > right) return;
        if (left == right) {
            res = nums[left];
            return;
        }
        int i = left;
        int j = right;
        int pivot = nums[left];
        while (i < j) {
            while (i < j && pivot <= nums[j]) {
                j--;
            }
            if (i < j) {
                nums[i] = nums[j];
                i++;
            }
            while (i < j && pivot >= nums[i]) {
                i++;
            }
            if (i < j) {
                nums[j] = nums[i];
                j--;
            }
        }
        nums[i] = pivot;
        if (i < target) quickSelect(nums, i + 1, right);
        if (i > target) quickSelect(nums, left, i - 1);
        if (i == target) res = nums[i];
    }

    int findKthLargest(vector<int> &nums, int k) {
        // 找第 k 大的就是找递增序列中下标为 target 的
        target = nums.size() - k;
        quickSelect(nums, 0, nums.size() - 1);
        return res;
    }
};

347. 前 K 个高频元素

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
class Solution {
public:
    void adjustHeap(vector<pair<int, int>> &array, int currentIndex, int len) {
        pair<int, int> temp = array[currentIndex];
        int leftChildIndex = 2 * currentIndex + 1;
        while (leftChildIndex <= (len - 1)) {
            // 从左右孩子中选取频率更小的
            if (leftChildIndex < (len - 1)
                && (array[leftChildIndex].second > array[leftChildIndex + 1].second))
                leftChildIndex++;
            if (array[leftChildIndex].second >= temp.second) break;
            array[currentIndex] = array[leftChildIndex];
            array[currentIndex] = array[leftChildIndex];
            currentIndex = leftChildIndex;
            leftChildIndex = 2 * currentIndex + 1;
        }
        array[currentIndex] = temp;
    }

    vector<int> topKFrequent(vector<int> &nums, int k) {
        // 统计频率
        unordered_map<int, int> map;
        for (auto &num: nums) {
            map[num]++;
        }
        // 用 map 统计出的数据初始化 heap
        vector<pair<int, int>> heap(begin(map), end(map));
        // 对前 k 个元素建立小顶堆
        for (int i = k / 2 - 1; i >= 0; i--) {
            adjustHeap(heap, i, k);
        }

        // 将后面的元素与当前小顶堆堆顶比较
        for (int i = k; i < heap.size(); ++i) {
            // 出现频率更大的话,才能进入小顶堆
            if (heap[i].second > heap[0].second) {
                heap[0] = heap[i];
                adjustHeap(heap, 0, k);
            }
        }

        vector<int> res(k);
        for (int i = 0; i < k; ++i) {
            res[i] = heap[i].first;
        }
        return res;
    }
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
class Solution {
public:
    // 小顶堆
    struct cmp {
        bool operator()(pair<int, int> &lhs, pair<int, int> &rhs) {
            return lhs.second > rhs.second;
        }
    };

    vector<int> topKFrequent(vector<int> &nums, int k) {
        // 记录出现次数
        unordered_map<int, int> map;
        for (int i = 0; i < nums.size(); i++) {
            map[nums[i]]++;
        }

        // 用 map 前 k 个元素初始化大小为 k 的小顶堆
        auto it = begin(map);
        // 迭代器后移 k 步
        advance(it, k);
        priority_queue<pair<int, int>, vector<pair<int, int>>, cmp> heap{begin(map), it};

        while (it != map.end()) {
            // 比小顶堆的堆顶元素出现的频率大的话就替换掉堆顶,并调整堆
            if (it->second > heap.top().second) {
                heap.pop();
                heap.emplace(it->first, it->second);
            }
            it++;
        }

        // 小顶堆中最终剩下的就是数组中出现频率前 k 高的元素
        vector<int> res;
        while (!heap.empty()) {
            res.emplace_back(heap.top().first);
            heap.pop();
        }
        return res;
    }
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
class Solution {
public:
    // 桶排序:空间换时间
    vector<int> topKFrequent(vector<int> &nums, int k) {
        // 记录频率,<元素,元素出现次数>
        unordered_map<int, int> map;
        for (auto &num: nums)
            map[num]++;

        // 记录最大频率,方便设置桶的总数
        int maxFrequency;
        for (auto it = begin(map); it != end(map); it++)
            maxFrequency = max(maxFrequency, it->second);

        // 桶:下标作为出现的次数,值为元素数组
        vector<vector<int>> bucket(maxFrequency + 1);

        // 根据元素出现次数,把元素放进对应的桶里
        for (auto it = begin(map); it != end(map); it++) {
            bucket[it->second].emplace_back(it->first);
        }

        vector<int> res;
        for (int i = bucket.size() - 1; i >= 0 && k > 0; i--) {
            // 根据出现频率由高到低,把有元素的桶里的元素加入到最终结果
            while (!bucket[i].empty()) {
                res.emplace_back(bucket[i].back());
                bucket[i].pop_back();
                k--;
            }
        }
        return res;
    }
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
class Solution {
public:

    int target;
    vector<int> res;

    // 根据后面 k 个最大的元素生产最终结果
    void generate(vector<pair<int, int>> &array) {
        for (int i = target; i < array.size(); ++i)
            res.emplace_back(array[i].first);
    }

    // 快速选择
    void quickSelect(vector<pair<int, int>> &array, int left, int right) {
        if (left > right) return;
        if (left == right) {
            generate(array);
            return;
        }
        pair<int, int> pivot = array[left];
        int i = left;
        int j = right;
        while (i < j) {
            while (i < j && pivot.second <= array[j].second) {
                j--;
            }
            if (i < j) {
                array[i] = array[j];
                i++;
            }
            while (i < j && pivot.second >= array[i].second) {
                i++;
            }
            if (i < j) {
                array[j] = array[i];
                j--;
            }
        }
        array[i] = pivot;
        if (i < target) quickSelect(array, i + 1, right);
        if (i > target) quickSelect(array, left, i - 1);
        if (i == target) generate(array);
    }

    vector<int> topKFrequent(vector<int> &nums, int k) {
        // 记录频率,<元素,元素出现次数>
        unordered_map<int, int> map;
        for (auto &num: nums)
            map[num]++;

        // 用统计出的频率初始化数组
        vector<pair<int, int>> array{begin(map), end(map)};
        // 找到第 k 个大的元素所在的下标 target 即可
        target = array.size() - k;

        quickSelect(array, 0, array.size() - 1);
        return res;
    }
};

295. 数据流的中位数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
class MedianFinder {
public:
    // 大顶堆存左边序列
    priority_queue<int, vector<int>, less<>> maxHeap;
    // 小顶堆存右边序列
    priority_queue<int, vector<int>, greater<>> minHeap;

    MedianFinder() {}

    void addNum(int num) {
        // 大顶堆空或者比大顶堆堆顶小,加入大顶堆
        if (maxHeap.empty() || num <= maxHeap.top()) {
            maxHeap.push(num);
            // 控制两个堆元素一样多
            if (minHeap.size() == maxHeap.size() - 2) {
                minHeap.push(maxHeap.top());
                maxHeap.pop();
            }
        } else {
            minHeap.push(num);
            // 如果不能一样多,就把多出的一个元素放到左边的大顶堆中
            if (minHeap.size() - 1 == maxHeap.size()) {
                maxHeap.push(minHeap.top());
                minHeap.pop();
            }
        }
    }

    double findMedian() {
        if (maxHeap.size() > minHeap.size()) return maxHeap.top();
        return (maxHeap.top() + minHeap.top()) / 2.0;
    }
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
// todo
class MedianFinder {
    multiset<int> nums;
    multiset<int>::iterator left, right;

public:
    MedianFinder() : left(nums.end()), right(nums.end()) {}

    void addNum(int num) {
        const size_t n = nums.size();

        nums.insert(num);
        if (!n) {
            left = right = nums.begin();
        } else if (n & 1) {
            if (num < *left) {
                left--;
            } else {
                right++;
            }
        } else {
            if (num > *left && num < *right) {
                left++;
                right--;
            } else if (num >= *right) {
                left++;
            } else {
                right--;
                left = right;
            }
        }
    }

    double findMedian() {
        return (*left + *right) / 2.0;
    }
};
1
// todo 进阶1、2

贪心

121. 买卖股票的最佳时机

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
int maxProfit(int *prices, int pricesSize) {
    if (pricesSize == 1) return 0;

    int res = 0;
    int min = prices[0];
    for (int i = 1; i < pricesSize; ++i) {
        if (prices[i] < min) {
            // 找到更少的购买价格
            min = prices[i];
        } else if (prices[i] - min > res) {
            // 检查能否获得更高的利润
            res = prices[i] - min;
        }
    }
    return res;
}

55. 跳跃游戏

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
bool res;
// 标记是否已经确定最终结果了
bool flag;

void recursive(int *nums, int numsSize, int left, int right) {
    if (flag) return;

    // 记录从[left, right]能到达的最远处
    int maxRight = right;
    for (int i = left; i <= right; ++i) {
        // 从当前位置能到的最远处
        int nextIndex = nums[i] + i;
        if (nextIndex > maxRight) maxRight = nextIndex;
    }

    if (maxRight >= numsSize - 1) {
        // 能到末尾
        res = true;
        flag = true;
    } else if (maxRight == right) {
        // 连自己的区间都跳不出去
        res = false;
        flag = true;
    } else {
        // 能跳出自己的区间,但还没到达末尾
        recursive(nums, numsSize, right + 1, maxRight);
    }
}

bool canJump(int *nums, int numsSize) {
    flag = false;
    recursive(nums, numsSize, 0, 0);
    return res;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
bool canJump(int *nums, int numsSize) {
    // 右边能到达的最远处
    int rightMax = 0;
    for (int i = 0; i < numsSize; ++i) {
        // 到不了i这个位置
        if (rightMax < i) return false;
        // 能到i的位置,检查从i处最远到哪
        if (rightMax < i + nums[i]) rightMax = i + nums[i];
        // 能到末尾,直接返回
        if (rightMax >= numsSize - 1) return true;
    }
    return true;
}

45. 跳跃游戏 II

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 动态规划
int jump(int *nums, int numsSize) {
    // 到达nums[i]的最小步数
    int dp[numsSize];
    for (int i = 0; i < numsSize; ++i) dp[i] = 0x7fffffff;
    dp[0] = 0;

    for (int i = 0; i < numsSize; ++i) {
        // 从i处能到达的范围
        int start = i + 1;
        int end = nums[i] + i;
        if (end >= numsSize) end = numsSize - 1;
        for (int j = start; j <= end; ++j)
            // 从当前i处多跳一步就到了
            if (dp[i] + 1 < dp[j]) dp[j] = dp[i] + 1;
    }
    return dp[numsSize - 1];
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// 递归写法
int steps;
// 标记是否已经确定最终结果了
bool flag;

void recursive(int *nums, int numsSize, int left, int right) {
    if (flag) return;

    if (right >= numsSize - 1) {
        // 能到末尾
        flag = true;
        return;
    }

    // 到不了,至少还得跳一次
    steps++;

    // 记录从[left, right]能到达的最远处
    int maxRight = right;
    for (int i = left; i <= right; ++i) {
        // 从当前位置能到的最远处
        int nextIndex = nums[i] + i;
        if (nextIndex > maxRight) maxRight = nextIndex;
    }
    // 处理子问题,从下个区间能到哪些位置
    recursive(nums, numsSize, right + 1, maxRight);
}

int jump(int *nums, int numsSize) {
    steps = 0;
    flag = false;
    recursive(nums, numsSize, 0, 0);
    return steps;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 迭代写法
int jump(int *nums, int numsSize) {
    int left = 0, right = 0;
    int steps = 0;

    while (right <= numsSize - 2) {
        // 到不了,至少还有跳一次
        steps++;
        // 右边能到的最远处
        int rightMax = right;
        for (int i = left; i <= right; ++i)
            if (rightMax < nums[i] + i) rightMax = nums[i] + i;
        left = right + 1;
        right = rightMax;
    }
    return steps;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 迭代优化版,少了个while循环处理step++的过程,放到for中处理
int jump(int *nums, int numsSize) {
    int right = 0;
    int steps = 0;
    int rightMax = 0;

    for (int i = 0; i <= numsSize - 2; ++i) {
        // 更新右边能到达的最远距离
        if (rightMax < nums[i] + i) rightMax = nums[i] + i;
        // [i, right]区间处理完了,更新right,处理下一步能到达的区间[right+1, xxx]
        if (i == right) {
            right = rightMax;
            steps++;
        }
    }

    return steps;
}

763. 划分字母区间

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
// 与56题合并区间类似
struct Pair {
    // 记录字符第一次出现的位置
    int firstPosition;
    // 记录是哪个字符
    char ch;
};

int cmp(const void *a, const void *b) {
    return (*(struct Pair *) a).firstPosition - (*(struct Pair *) b).firstPosition;
}

int *partitionLabels(char *s, int *returnSize) {
    const int sizeOfChar = 26;
    // 记录第一次出现的位置
    int firstPositions[sizeOfChar];
    // 记录最后一次出现的位置
    int lastPositions[sizeOfChar];
    memset(firstPositions, -1, sizeof(firstPositions));
    memset(lastPositions, -1, sizeof(lastPositions));

    // strlen不要放到for中,不然每次循环都要计算下长度
    int len = strlen(s);
    for (int i = 0; i < len; ++i) {
        if (firstPositions[s[i] - 'a'] == -1) firstPositions[s[i] - 'a'] = i;
        lastPositions[s[i] - 'a'] = i;
    }
    // 对于每个在s中出现的字符,都有一个区间[firstPositions, lastPosition]
    // 不同两个字符要是区间相交了,就把区间合并,最终把无法合并的区间追加到结果中

    struct Pair *pair = (struct Pair *) malloc(sizeof(struct Pair) * sizeOfChar);
    // 实际出现的字母种数
    int pairSize = 0;
    for (int i = 0; i < sizeOfChar; ++i) {
        // 这个字符没出现就不处理
        if (firstPositions[i] == -1) continue;
        pair[pairSize].firstPosition = firstPositions[i];
        pair[pairSize].ch = i + 'a';
        pairSize++;
    }

    // 按字符首次出现的位置排序,方便合并区间
    qsort(pair, pairSize, sizeof(struct Pair), cmp);

    *returnSize = 0;
    int *res = (int *) malloc(sizeof(int) * len);
    int index = 0;
    while (index < pairSize) {
        // 第一个区间的左端点
        int left = pair[index].firstPosition;
        // 第一个区间的待定右端点,可能会和其他字符的区间合并
        int right = lastPositions[pair[index].ch - 'a'];
        // 移到pair中的下个字符
        index++;

        while (index < pairSize && pair[index].firstPosition < right) {
            // 和下个字符的区间有重叠,就合并区间,右端点取较大者
            if (lastPositions[pair[index].ch - 'a'] > right)
                right = lastPositions[pair[index].ch - 'a'];
            index++;
        }
        // 记录区间长度
        res[(*returnSize)++] = right - left + 1;
    }

    return res;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
int *partitionLabels(char *s, int *returnSize) {
    int len = strlen(s);
    // 第一次遍历字符串记录最后一次出现的位置
    int lastPositions[26];
    for (int i = 0; i < len; i++)
        lastPositions[s[i] - 'a'] = i;

    *returnSize = 0;
    int *res = malloc(sizeof(int) * len);
    int left = 0, right = 0;

    // 第二次遍历字符串
    // 从left遍历到right,若right更新了,就继续遍历到新的right
    // 否则,遍历到right时,表明其中所有字符的右边界都小于等于right,可以归为一个区间
    for (int i = 0; i < len; i++) {
        if (lastPositions[s[i] - 'a'] > right)
            right = lastPositions[s[i] - 'a'];
        if (i == right) {
            res[(*returnSize)++] = right - left + 1;
            left = right + 1;
        }
    }
    return res;
}

动态规划

70. 爬楼梯

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
int climbStairs(int n) {
    int dp[46];
    // 一次爬一层,只有一种方法
    dp[1] = 1;
    // 两次爬一层或者一次爬两层,一共两种方法
    dp[2] = 2;
    int i = 3;
    while (i <= n) {
        // i层可由i-2层爬两个台阶到达,或者由i-1层爬一个台阶到达
        // 爬到i层的方法总数为这两种爬法的方法总数和
        dp[i] = dp[i - 2] + dp[i - 1];
        i++;
    }
    return dp[n];
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 空间复杂度O(1)
int climbStairs(int n) {
    if (n < 3) return n;
    int left = 1;
    int mid = 2;
    int right;
    int count = n - 2;
    while (count-- > 0) {
        right = left + mid;
        left = mid;
        mid = right;
    }
    return right;
}

118. 杨辉三角

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
int **generate(int numRows, int *returnSize, int **returnColumnSizes) {
    *returnSize = numRows;
    *returnColumnSizes = (int *) malloc(sizeof(int) * numRows);
    int **res = (int **) malloc(sizeof(int *) * numRows);
    for (int i = 0; i < numRows; ++i) {
        res[i] = (int *) malloc(sizeof(int) * (i + 1));
        (*returnColumnSizes)[i] = i + 1;
    }

    for (int i = 0; i < numRows; ++i) {
        res[i][0] = 1;
        res[i][i] = 1;
        for (int j = 1; j <= i - 1; ++j) {
            res[i][j] = res[i - 1][j - 1] + res[i - 1][j];
        }
    }

    return res;
}

198. 打家劫舍

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
int max(int a, int b) {
    return a > b ? a : b;
}

int rob(int *nums, int numsSize) {
    if (numsSize == 1) return nums[0];
    // dp[i]表示偷i间房子的最大金额
    int dp[numsSize];
    dp[0] = nums[0];
    dp[1] = max(nums[0], nums[1]);

    for (int i = 2; i < numsSize; ++i) {
        // 当前位置偷,dp[i] = dp[i-2] + nums[i]
        // 当前位置不偷,dp[i] = dp[i-1]
        dp[i] = max(dp[i-1], dp[i-2] + nums[i]);
    }

    return dp[numsSize - 1];
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
int max(int a, int b) {
    return a > b ? a : b;
}

// 空间优化
int rob(int *nums, int numsSize) {
    int left = 0;
    int mid = 0;
    int right = 0;

    for (int i = 0; i < numsSize; ++i) {
        right = max(mid, left + nums[i]);
        left = mid;
        mid = right;
    }

    return right;
}

279. 完全平方数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
int min(int a, int b) {
    return a > b ? b : a;
}

int numSquares(int n) {
    int dp[n + 1];
    dp[0] = 0;
    for (int i = 1; i <= n; ++i) {
        // 最坏情况是i个1
        dp[i] = i;
        // 枚举1到sqrt(i)
        for (int j = 1; j * j <= i; ++j) {
            // i可有一个完全平方数j*j与余数i-j*j构成
            // i-j*j最少需要dp[i-j*j]个平方数相加构成,j*j只需一个平方数就能构成
            dp[i] = min(dp[i], dp[i - j * j] + 1);
        }
    }

    return dp[n];
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// todo 四平方和定理
// 判断是否为完全平方数
bool isPerfectSquare(int x) {
    int y = sqrt(x);
    return y * y == x;
}

// 判断是否能表示为 4^k*(8m+7)
bool checkAnswer4(int x) {
    while (x % 4 == 0) {
        x /= 4;
    }
    return x % 8 == 7;
}

int numSquares(int n) {
    if (isPerfectSquare(n)) {
        return 1;
    }
    if (checkAnswer4(n)) {
        return 4;
    }
    for (int i = 1; i * i <= n; i++) {
        int j = n - i * i;
        if (isPerfectSquare(j)) {
            return 2;
        }
    }
    return 3;
}

322. 零钱兑换

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
class Solution {
public:
    // 记忆化搜索
    vector<int> count;

    int recursive(vector<int> &coins, int rem) {
        if (rem < 0) return -1;
        if (rem == 0) return 0;
        // 已经有总额为 rem 的最少硬币数,直接返回
        if (count[rem - 1] != 0) return count[rem - 1];
        // 计算总额为 rem 的最少硬币数
        int Min = INT_MAX;
        for (int coin: coins) {
            // 组成金额 rem 最少的硬币数就是组成金额 rem - coin 的最少硬币数量 + 1
            int res = recursive(coins, rem - coin);
            // 从中取最小值
            if (res >= 0) Min = min(Min, res + 1);
        }
        count[rem - 1] = Min == INT_MAX ? -1 : Min;
        return count[rem - 1];
    }

    int coinChange(vector<int> &coins, int amount) {
        if (amount < 1) return 0;
        count.resize(amount);
        return recursive(coins, amount);
    }
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Solution {
public:
    int coinChange(vector<int> &coins, int amount) {
        // dp[i] 为总额为 i 的最少硬币数
        vector<int> dp(amount + 1, amount + 1);
        // 总额为 0 的硬币数为 0
        dp[0] = 0;
        // 自下而上
        for (int sum = 1; sum <= amount; ++sum) {
            for (auto &coin: coins) {
                // 硬币面额比总额还大
                if (coin > sum) continue;
                // 更新最小值
                dp[sum] = min(dp[sum], dp[sum - coin] + 1);
            }
        }
        return dp[amount] > amount ? -1 : dp[amount];
    }
};

139. 单词拆分

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Solution {
public:
    bool wordBreak(string s, vector<string> &wordDict) {
        // 存入集合,方便判断
        unordered_set<string> dict;
        for (auto &word: wordDict) dict.insert(word);

        // dp[i] 表示能否成功拆分 s[0,i-1]
        vector<bool> dp(s.size() + 1, false);
        dp[0] = true;
        for (int i = 1; i <= s.size(); ++i) {
            for (int j = 0; j < i; ++j) {
                // s[0,j-1] 这 j 个字符构成的字符串能被拆分的情况下,判断子串 s[j, i) 是否在字典中
                if (dp[j] && dict.find(s.substr(j, i - j)) != dict.end()) {
                    dp[i] = true;
                    break;
                }
            }
        }

        return dp[s.size()];
    }
};

300. 最长递增子序列

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Solution {
public:
    int lengthOfLIS(vector<int> &nums) {
        int len = nums.size();
        if (len == 0) return 0;
        vector<int> dp(len, 0);
        for (int i = 0; i < len; ++i) {
            dp[i] = 1;
            for (int j = 0; j < i; ++j) {
                if (nums[j] < nums[i])
                    dp[i] = max(dp[i], dp[j] + 1);
            }
        }
        return *max_element(dp.begin(), dp.end());
    }
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// 贪心 + 二分
class Solution {
public:
    int lengthOfLIS(vector<int> &nums) {
        int length = 1, len = nums.size();
        if (len == 0) return 0;
        vector<int> d(len + 1, 0);
        d[length] = nums[0];
        for (int i = 1; i < len; ++i) {
            if (nums[i] > d[length]) {
                d[++length] = nums[i];
            } else {
                // 如果找不到说明所有的数都比 nums[i] 大,此时要更新 d[1],所以这里将 pos 设为 0
                int l = 1, r = length, pos = 0;
                while (l <= r) {
                    int mid = (l + r) >> 1;
                    if (d[mid] < nums[i]) {
                        pos = mid;
                        l = mid + 1;
                    } else {
                        r = mid - 1;
                    }
                }
                d[pos + 1] = nums[i];
            }
        }
        return length;
    }
};

152. 乘积最大子数组

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
int min(int a, int b) {
    return a > b ? b : a;
}

int max(int a, int b) {
    return a > b ? a : b;
}

int maxProduct(int *nums, int numsSize) {
    int res = nums[0];
    // 以i位置结尾的子数组乘积的最大值和最小值
    int minDP = nums[0];
    int maxDP = nums[0];
    // 最大值可能是当前元素,或者是以i-1位置结尾的最小乘积与nums[i]相乘(负数乘负数),或者以i-1位置结尾的最大乘积与nums[i]相乘
    // 在递推过程中,记录以i-1结尾的子数组乘积的最值
    for (int i = 1, curMin, curMax; i < numsSize; ++i) {
        curMin = min(nums[i], min(nums[i] * minDP, nums[i] * maxDP));
        curMax = max(nums[i], max(nums[i] * minDP, nums[i] * maxDP));
        minDP = curMin;
        maxDP = curMax;
        res = max(res, maxDP);
    }
    return res;
}

416. 分割等和子集

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
class Solution {
public:
    bool canPartition(vector<int> &nums) {
        int len = nums.size();
        if (len < 2) return false;

        int sum = accumulate(nums.begin(), nums.end(), 0);
        int maxNum = *max_element(nums.begin(), nums.end());
        if (sum & 1) return false;
        int target = sum / 2;
        if (maxNum > target) return false;
        
        vector<vector<int>> dp(len, vector<int>(target + 1, 0));
        for (int i = 0; i < len; i++) dp[i][0] = true;
        dp[0][nums[0]] = true;
        for (int i = 1; i < len; i++) {
            int num = nums[i];
            for (int j = 1; j <= target; j++) {
                if (j >= num) {
                    dp[i][j] = dp[i - 1][j] | dp[i - 1][j - num];
                } else {
                    dp[i][j] = dp[i - 1][j];
                }
            }
        }
        return dp[len - 1][target];
    }
};

32. 最长有效括号

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
int longestValidParentheses(char *s) {
    int len = strlen(s);
    if (len <= 1) return 0;

    // dp[i]表示以s[i]结尾的最长有效括号的长度
    int dp[len];
    for (int i = 0; i < len; ++i) dp[i] = 0;
    int max = 0;
    for (int i = 1; i < len; ++i) {
        // 以'('结尾,无法形成有效括号
        if (s[i] == '(') {
            dp[i] = 0;
            continue;
        }
        // 以')'结尾
        int index = i - 1 - dp[i - 1];
        // 以s[i-1]为结尾的最长有效括号的开头的左边一个字符
        if (index >= 0) {
            char ch = s[index];
            if (ch == '(') {
                dp[i] = dp[i - 1] + 2;
                if (index - 1 >= 0) dp[i] += dp[index - 1];
            } else {
                dp[i] = 0;
            }
        } else {
            dp[i] = 0;
        }
        // 记录最大
        if (dp[i] > max) max = dp[i];
    }
    return max;
}

多维动态规划

62. 不同路径

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
int uniquePaths(int m, int n) {
    // 记录到格子(i,j)的路径总数
    int dp[m][n];

    // 初始条件
    // 从(0,0)到第一列的任何一个格子的路径只有一条,就是从上往下
    for (int i = 0; i < m; ++i) dp[i][0] = 1;
    // 从(0,0)到第一行的任何一个格子的路径只有一条,就是从左往右
    for (int i = 0; i < n; ++i) dp[0][i] = 1;

    for (int i = 1; i < m; ++i) {
        for (int j = 1; j < n; ++j) {
            // 状态转移方程
            // (i,j)只能由左边的格子或者上面的格子走过来,dp[i][j]就是这两种途径的路径和
            dp[i][j] = dp[i - 1][j] + dp[i][j - 1];
        }
    }
    return dp[m - 1][n - 1];
}
1
// todo 空间优化
1
// 排列组合

64. 最小路径和

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
int min(int a, int b) {
    return a > b ? b : a;
}

// 自底向上+空间压缩
int minPathSum(int **grid, int gridSize, int *gridColSize) {
    int rowSize = gridSize;
    int columnSize = *gridColSize;
    // 一行一行保存最小路径和
    int dp[columnSize];
    dp[0] = grid[0][0];
    // 第一行只和左边的一个元素有关
    for (int i = 1; i < columnSize; ++i)
        dp[i] = dp[i - 1] + grid[0][i];

    for (int i = 1; i < rowSize; ++i) {
        // 每行的首元素只和上一行同位置元素有关
        dp[0] += grid[i][0];
        // 其他行的非首元素和当前行左边的元素和上一行的同位置元素有关
        for (int j = 1; j < columnSize; ++j)
            dp[j] = min(dp[j - 1], dp[j]) + grid[i][j];
    }

    return dp[columnSize - 1];
}

5. 最长回文子串

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
class Solution {
public:
    string longestPalindrome(string s) {
        int len = s.length();
        // 动态规划表,dp[i][j] 为 true 表示 s[i,j] 是回文串
        vector<vector<bool>> dp(len, vector<bool>(len, false));

        // 主对角线上的 dp[i][i] 是单个元素,都是回文
        for (int i = 0; i < len; ++i) dp[i][i] = true;

        // 最长回文子串的起始下标和长度
        int start{}, length{1};
        // 判断是否是回文的子串的长度
        for (int gap = 1; gap < len; ++gap) {
            for (int i = 0; i + gap < len; ++i) {
                int j = i + gap;
                // 内部是否回文
                bool inner = gap == 1 || dp[i + 1][j - 1];
                // 两端相等且内部也是回文时,才是回文
                dp[i][j] = (s[i] == s[j]) && inner;
                // 是回文就更新长度
                if (dp[i][j]) {
                    start = i;
                    length = gap + 1;
                }
            }
        }

        return s.substr(start, length);
    }
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
class Solution {
public:
    int start{}, length{1};
    int len{};

    // 从中间向两边扩展
    void expand(string s, int left, int right) {
        while (left >= 0 && right < len) {
            // 构不成回文
            if (s[left] != s[right]) break;
            // 能构成就继续向外扩展
            left--;
            right++;
        }
        if (right - left - 1 > length) {
            start = left + 1;
            length = right - left - 1;
        }
    }


    string longestPalindrome(string s) {
        len = s.length();

        for (int i = 0; i < len; ++i) {
            // 中心只有一个元素
            expand(s, i, i);
            // 中心有两个元素
            expand(s, i, i + 1);
        }

        return s.substr(start, length);
    }
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
// todo: Manacher 算法
class Solution {
public:
    int expand(const string &s, int left, int right) {
        while (left >= 0 && right < s.size() && s[left] == s[right]) {
            --left;
            ++right;
        }
        return (right - left - 2) / 2;
    }

    string longestPalindrome(string s) {
        int start = 0, end = -1;
        string t = "#";
        for (char c: s) {
            t += c;
            t += '#';
        }
        t += '#';
        s = t;

        vector<int> arm_len;
        int right = -1, j = -1;
        for (int i = 0; i < s.size(); ++i) {
            int cur_arm_len;
            if (right >= i) {
                int i_sym = j * 2 - i;
                int min_arm_len = min(arm_len[i_sym], right - i);
                cur_arm_len = expand(s, i - min_arm_len, i + min_arm_len);
            } else {
                cur_arm_len = expand(s, i, i);
            }
            arm_len.push_back(cur_arm_len);
            if (i + cur_arm_len > right) {
                j = i;
                right = i + cur_arm_len;
            }
            if (cur_arm_len * 2 + 1 > end - start) {
                start = i - cur_arm_len;
                end = i + cur_arm_len;
            }
        }

        string ans;
        for (int i = start; i <= end; ++i) {
            if (s[i] != '#') {
                ans += s[i];
            }
        }
        return ans;
    }
};

1143. 最长公共子序列

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
int max(int a, int b) {
    return a > b ? a : b;
}

int **dp;
int length1;
int length2;

// 返回前缀长为len1和len2的最长公共子序列长度
int recursive(char *s1, char *s2, int len1, int len2) {
    if (len1 == 0 || len2 == 0) return 0;
    if (dp[len1][len2] != -1) return dp[len1][len2];
    if (s1[len1 - 1] == s2[len2 - 1]) {
        dp[len1][len2] = recursive(s1, s2, len1 - 1, len2 - 1) + 1;
    } else {
        // 优化:省去了recursive(s1, s2, len1 - 1, len2 - 1),因为范围包括在下面的两个范围内了
        dp[len1][len2] = max(recursive(s1, s2, len1 - 1, len2), recursive(s1, s2, len1, len2 - 1));
    }
    return dp[len1][len2];
}

int longestCommonSubsequence(char *text1, char *text2) {
    length1 = strlen(text1);
    length2 = strlen(text2);
    dp = (int **) malloc(sizeof(int *) * (length1 + 1));
    for (int i = 0; i <= length1; ++i) {
        dp[i] = (int *) malloc(sizeof(int) * (length2 + 1));
        memset(dp[i], -1, sizeof(int) * (length2 + 1));
    }

    return recursive(text1, text2, length1, length2);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
int max(int a, int b) {
    return a > b ? a : b;
}

int longestCommonSubsequence(char *text1, char *text2) {
    int length1 = strlen(text1);
    int length2 = strlen(text2);
    int dp[length1 + 1][length2 + 1];
    // 第一行和第一列都是0
    for (int i = 0; i <= length1; ++i) dp[i][0] = 0;
    for (int i = 0; i <= length2; ++i) dp[0][i] = 0;
    // 和左边,上边,左上角的元素有关
    for (int len1 = 1; len1 <= length1; ++len1) {
        for (int len2 = 1; len2 <= length2; ++len2) {
            if (text1[len1 - 1] == text2[len2 - 1])
                dp[len1][len2] = dp[len1 - 1][len2 - 1] + 1;
            else
                dp[len1][len2] = max(dp[len1 - 1][len2], dp[len1][len2 - 1]);
        }
    }

    return dp[length1][length2];
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
int max(int a, int b) {
    return a > b ? a : b;
}

int longestCommonSubsequence(char *text1, char *text2) {
    char *s1, *s2;
    // s2为较短
    if (strlen(text1) < strlen(text2)) {
        s1 = text2;
        s2 = text1;
    } else {
        s1 = text1;
        s2 = text2;
    }
    int length1 = strlen(s1);
    int length2 = strlen(s2);
    // 长度较短但滚动次数较多的数组,
    int dp[length2 + 1];
    // 第0行全0
    memset(dp, 0, sizeof(int) * (length2 + 1));
    for (int len1 = 1; len1 <= length1; ++len1) {
        // 左上角元素
        int leftUp = dp[0];
        // 修改前暂存当前元素
        int temp;
        for (int len2 = 1; len2 <= length2; ++len2) {
            temp = dp[len2];
            if (s1[len1 - 1] == s2[len2 - 1])
                // 与左上角有关
                dp[len2] = leftUp + 1;
            else
                // 与左边和上边的最大值有关
                dp[len2] = max(dp[len2 - 1], dp[len2]);
            leftUp = temp;
        }
    }
    return dp[length2];
}

72. 编辑距离

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
int min(int a, int b) {
    return a > b ? b : a;
}

// 插入、删除、替换一个字符的代价分别为a,b,c
int editDistance(char *s1, char *s2, int a, int b, int c) {
    int len1 = strlen(s1);
    int len2 = strlen(s2);
    // dp[i][j]表示s1[前缀长度i]变成s2[前缀长度j]的最小代价
    int dp[len1 + 1][len2 + 1];
    // 空字符串到空字符串没有代价
    dp[0][0] = 0;
    // 空字符串到s2[前缀长度i]需要插入相应字符
    for (int i = 1; i <= len2; ++i)
        dp[0][i] = i * a;
    // s1[前缀长度i]到空字符串需要删除相应字符
    for (int i = 1; i <= len1; ++i)
        dp[i][0] = i * b;
    // 首行首列已经填完,剩下的格子和左侧、上方、左上方的格子有关
    for (int i = 1; i <= len1; ++i) {
        for (int j = 1; j <= len2; ++j) {
            // 1. s1[i-1]参与
            //      1.1 s1[i-1]变成s2[j-1]
            //          1.1.1 末尾字符相同,即s1[i-1]等于s2[j-1]时,代价等于dp[i-1][j-1](情况p1)
            //          1.1.2 末尾字符不相同,代价等于dp[i-1][j-1]加上替换字符的代价(情况p2)
            //      1.2 s1[i-1]不变成s2[j-1]
            //          1.2.1 s1[0~i-1]变成s2[0~j-2]最后再插入字符s2[j-1],代价等于dp[i][j-1]加上插入字符的代价(情况p3)
            // 2. s1[i-1]不参与
            //      2.1 即删除s1[i-1],让s1[0~i-2]变成s2[0~j-1],代价等于dp[i-1][j]加上删除字符的代价(情况p4)
             int p1 = 0x7fffffff;
            if (s1[i - 1] == s2[j - 1])
                p1 = dp[i - 1][j - 1];
            int p2 = 0x7fffffff;
            if (s1[i - 1] != s2[j - 1])
                p2 = dp[i - 1][j - 1] + c;
            int p3 = dp[i][j - 1] + a;
            int p4 = dp[i - 1][j] + b;
            dp[i][j] = min(min(p1, p2), min(p3, p4));
        }
    }
    return dp[len1][len2];
}

int minDistance(char *word1, char *word2) {
    return editDistance(word1, word2, 1, 1, 1);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
int min(int a, int b) {
    return a > b ? b : a;
}

// 插入、删除、替换一个字符的代价分别为a,b,c
int editDistance(char *s1, char *s2, int a, int b, int c) {
    int len1 = strlen(s1);
    int len2 = strlen(s2);
    // dp[i][j]表示s1[前缀长度i]变成s2[前缀长度j]的最小代价
    int dp[len1 + 1][len2 + 1];
    // 空字符串到空字符串没有代价
    dp[0][0] = 0;
    // 空字符串到s2[前缀长度i]需要插入相应字符
    for (int i = 1; i <= len2; ++i)
        dp[0][i] = i * a;
    // s1[前缀长度i]到空字符串需要删除相应字符
    for (int i = 1; i <= len1; ++i)
        dp[i][0] = i * b;
    // 首行首列已经填完,剩下的格子和左侧、上方、左上方的格子有关
    for (int i = 1; i <= len1; ++i) {
        for (int j = 1; j <= len2; ++j) {
            // 根据末尾字符是否相同划分
            if (s1[i - 1] == s2[j - 1]) {
                // 相等时,代价就是之前dp[i - 1][j - 1]的代价
                dp[i][j] = dp[i - 1][j - 1];
            } else {
                // 不等时,分为插入删除和替换三种可能
                dp[i][j] = min(min(dp[i - 1][j - 1] + c, dp[i][j - 1] + a), dp[i - 1][j] + b);
            }
        }
    }
    return dp[len1][len2];
}

// 易理解版
int minDistance(char *word1, char *word2) {
    return editDistance(word1, word2, 1, 1, 1);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
int min(int a, int b) {
    return a > b ? b : a;
}

// 插入、删除、替换一个字符的代价分别为a,b,c
int editDistance(char *s1, char *s2, int a, int b, int c) {
    int len1 = strlen(s1);
    int len2 = strlen(s2);
    int dp[len2 + 1];
    // 空字符串到空字符串没有代价
    dp[0] = 0;
    // 第一行除了首元素外,其他位置表示从空字符串到s2[前缀长度i]需要插入相应字符的总代价
    for (int i = 1; i <= len2; ++i)
        dp[i] = i * a;

    int leftUp;
    int backup;
    for (int i = 1; i <= len1; ++i) {
        leftUp = (i - 1) * b;
        // s1[前缀长度i]到空字符串需要删除相应字符
        dp[0] = i * b;
        for (int j = 1; j <= len2; ++j) {
            backup = dp[j];
            // 根据末尾字符是否相同划分
            if (s1[i - 1] == s2[j - 1]) {
                // 相等时,代价就是之前dp[i - 1][j - 1]的代价
                dp[j] = leftUp;
            } else {
                // 不等时,分为插入删除和替换三种可能
                dp[j] = min(min(leftUp + c, dp[j - 1] + a), dp[j] + b);
            }
            leftUp = backup;
        }
    }
    return dp[len2];
}

// 空间压缩,类似于最长公共子序列的空间压缩
int minDistance(char *word1, char *word2) {
    return editDistance(word1, word2, 1, 1, 1);
}

技巧

136. 只出现一次的数字

1
2
3
4
5
6
7
int singleNumber(int *nums, int numsSize) {
    int XOR = nums[0];
    for (int i = 1; i < numsSize; ++i) {
        XOR ^= nums[i];
    }
    return XOR;
}

169. 多数元素

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// todo Boyer-Moore 投票算法
// Boyer-Moore 算法的正确性较难证明
int majorityElement(int *nums, int numsSize) {
    int candidate = nums[0];
    int count = 0;
    
    for (int i = 0; i < numsSize; ++i) {
        // 如果投的人是候选人,则计数加一
        // 然后检查下一张选票
        if (nums[i] == candidate) {
            count++;
            continue;
        }
        
        // 如果投的人不是候选人,并且计数器为0
        // 则这个人就是新的候选人,计数加一
        if (count == 0) {
            candidate = nums[i];
            count++;
            continue;
        }

        // 如果投的人不是候选人,并且计数器不为0
        // 说明这个人和候选人不同,则进行抵消(两两相消)
        count--;
    }
    return candidate;
}
1
// todo 分治
1
// todo 随机化

75. 颜色分类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
void swap(int *nums, int left, int right) {
    int temp = nums[left];
    nums[left] = nums[right];
    nums[right] = temp;
}

void sortColors(int *nums, int numsSize) {
    int left = 0, right = numsSize - 1;

    while (left <= right) {
        while (left <= right && nums[left] == 0) left++;
        while (left <= right && nums[right] == 2) right--;
        if (left > right) break;
        // left不是0且right也不是2

        if (nums[left] == 1) {
            int index = left + 1;
            // 找到下一个不是1的位置
            while (index <= right && nums[index] == 1) index++;
            if (index > right) break;
            // 和left或者right交换一次,然后进入下次循环
            if (nums[index] == 0) {
                swap(nums, left, index);
                left++;
            } else if (nums[index] == 2) {
                swap(nums, right, index);
                right--;
            }
        } else if (nums[left] == 2) {
            swap(nums, left, right);
            right--;
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
void swap(int *nums, int left, int right) {
    int temp = nums[left];
    nums[left] = nums[right];
    nums[right] = temp;
}

void sortColors(int *nums, int numsSize) {
    if (numsSize == 1) return;
    // 下个0应该放的位置
    int left = 0;
    // 下个2应该放到位置
    int right = numsSize - 1;
    int index = 0;

    while (index <= right) {
        if (nums[index] == 0) {
            // 所有的0移到左边
            swap(nums, left, index);
            left++;
            // index至少时从left开始的,left左边全都是0
            if (left > index) index = left;
        } else if (nums[index] == 2) {
            // 所有的2移到右边
            swap(nums, right, index);
            right--;
        } else if (nums[index] == 1) {
            // 当前位置时1才会后移index,否则就把当前位置的0或者2放到两端
            index++;
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
void swap(int *nums, int left, int right) {
    int temp = nums[left];
    nums[left] = nums[right];
    nums[right] = temp;
}

void sortColors(int *nums, int numsSize) {
    int left = 0;
    int right = numsSize - 1;
    int index = 0;
    while (index <= right) {
        // 是2就全移到右边
        while (index <= right && nums[index] == 2) {
            swap(nums, index, right);
            right--;
        }
        // 是0就移到左边
        if (nums[index] == 0) {
            swap(nums, index, left);
            left++;
        }
        index++;
    }
}

31. 下一个排列

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
void swap(int *nums, int left, int right) {
    int temp = nums[left];
    nums[left] = nums[right];
    nums[right] = temp;
}

void reverseArray(int *nums, int left, int right) {
    while (left < right)
        swap(nums, left++, right--);
}

// todo
// 需要用一个左边的较小数和一个右边的较大数交换,才能得到后面的一个排列
// 同时,较小数要尽量靠右,较大数尽量小,尽量减小变大的幅度
// 交换后,还要将换位后的较大的数右边重新升序排列,尽量减小变大的幅度
void nextPermutation(int *nums, int numsSize) {
    int left = numsSize - 2;
    // 从右往左找较小数,左右边的一个升序对的两个元素中,左边的就是较小数
    while (left >= 0 && nums[left] >= nums[left + 1])
        left--;
    // 能找到这个较小数时
    if (left >= 0) {
        int right = numsSize - 1;
        // 从右往左找较大数,找第一个大于较小数的数
        while (right >= 0 && nums[left] >= nums[right])
            right--;
        swap(nums, left, right);
    }
    // 1.交换较小数和较大数后,区间[left+1, numsSize-1]必为降序
    // 2.找不到较小数较大数时,说明已经时降序序列,直接改成最小的升序序列
    reverseArray(nums, left + 1, numsSize - 1);
}

287. 寻找重复数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 类似环形链表找入口节点,nums数组类似静态链表
int findDuplicate(int *nums, int numsSize) {
    int slow = 0, fast = 0;
    slow = nums[slow];
    fast = nums[nums[fast]];
    while (slow != fast) {
        // slow = slow.next
        slow = nums[slow];
        // fast = fast.next.next
        fast = nums[nums[fast]];
    }
    fast = 0;
    while (slow != fast) {
        slow = nums[slow];
        fast = nums[fast];
    }
    return slow;
}
本文由作者按照 CC BY 4.0 进行授权