leetcode刷题记录

本文记录了在LeetCode上刷题的过程,涵盖了数组、链表、字符串、二叉树、动态规划等多个主题,包括经典题目如两数之和、最长无重复字符子串、有效括号等,并分享了解题思路,如二分法、深度优先搜索、广度优先搜索等算法应用。
摘要由CSDN通过智能技术生成

文章目录

1. 两数之和
//使用哈希表来存储目标值与当前元素的差,如果未来某个值对于该差值,则找到一个结果。返回。
class Solution{
public:
    vector<int> twoSum(vector<int> &nums, int target){
        unordered_map<int, int> hashTab;
        for (int i = 0; i < nums.size(); ++i)
        {
            if (hashTab.find(nums[i]) != hashTab.end()) //如果哈希表中存在当前值
                return {hashTab[nums[i]], i};
            hashTab[target - nums[i]] = i;
        }
        return {};
    }
};
2. 两数相加
//模拟加法过程,和链表操作。
class Solution {
public:
    ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
        ListNode *head = l1, *pre = l1;
        int carry = 0;
        while (l1 && l2){ //同时遍历两个链表
            int sum = l1->val + l2->val + carry;
            carry = sum / 10, l1->val = sum % 10;
            pre = l1, l1 = l1->next;
            l2 = l2->next;
        }
        if (!l1) //原来存储结果的链表完了,将另一个链表剩余部分作为存放结果的地方。
            pre->next = l2, l1 = l2;
        while (l1){
            int sum = l1->val + carry;
            carry = sum / 10, l1->val = sum % 10;
            pre = l1, l1 = l1->next;
        }
        if (carry) //有进位
            pre->next = new ListNode(1);
        return head;
    }
};
3. 无重复字符的最长子串
//设立一个数组来存储当前窗口中各元素的个数,当新加入的元素导致其出现个数超过2,需要移动左边界,使窗口内的各元素出现次数为1.
//leetcode使用的固定左边界,扩展右边界。
class Solution {
public:
    int lengthOfLongestSubstring(string s) {
        int left = 0, right = 0, len = 0;
        vector<int> cnt(128, 0);
        while (right < s.size()) {
            cnt[s[right]]++;
            while (cnt[s[right]] > 1)//查找值等于当前元素的位置,左边界移到下一位置,使窗口内各字符不会重复出现
                cnt[s[left++]]--;
            right++;
            len = max(len, right - left);
        }
        return len;
    }
};
4. 寻找两个正序数组的中位数
//依次合并两个数组,直到到达合并数组的中间时,可以直接求得中位数。
class Solution {
public:
    double findMedianSortedArrays(vector<int> &nums1, vector<int> &nums2){
        int i = 0, j = 0, cnt = 0;
        int m = nums2.size(), n = nums1.size();
        int mid = (m + n - 1) / 2;
        while (cnt < mid && i < n && j < m){ //找中位数的位置,丢弃前(m+n)/2-1个元素
            if (nums1[i] < nums2[j])
                i++;
            else
                j++;
            cnt++;
        }
        if (cnt < mid){ //一个数组遍历完
            while (cnt < mid){
                i == n ? j++ : i++;
                cnt++;
            }
        }

        double x;
        if ((m + n) % 2){  //根据数组个数的奇偶性确定中位数。
            if (i == n)
                x = nums2[j];
            else if (j == m)
                x = nums1[i];
            else
                x = min(nums1[i], nums2[j]);
        }
        else{
            if (i == n)
                x = nums2[j] + nums2[j + 1];
            else if (j == m)
                x = nums1[i] + nums1[i + 1];
            else{//需要找接下来最小的两个数。
                int min1 = min(nums1[i], nums2[j]), min2;
                if (nums1[i] < nums2[j])
                    i++;
                else
                    j++;
                if (i == n)
                    min2 = nums2[j];
                else if (j == m)
                    min2 = nums1[i];
                else
                    min2 = min(nums1[i], nums2[j]);
                x = min1 + min2;
            }
            x /= 2;
        }
        return x;
    }
};
//相同的方法,更少的代码。
class Solution {
public:
    double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
        int left, right, i = 0, j = 0, k = 0; //left和right用来存放第(m+n)/2和第(m+n)/2+1小的值
        int n = nums1.size(), m = nums2.size();
        while (k <= (m + n) / 2) {
            left = right;
            if (i < n && (j >= m || nums1[i] < nums2[j]))
                right = nums1[i++];
            else
                right = nums2[j++];
            k++;
        }
        if ((m + n) & 1)
            return right;
        else
            return (left + right) / 2.0;
    }
};
//该题为目标为找出正确的划分线。可以选择在小的数组进行查找划分的位置,
20. 有效的括号
//方法:堆栈使用的典型案例,左括号压入,遇到右括号弹出。其他情况无效。
#define ISLEFT(c) (c == '(' || c == '{' || c == '[')
#define ISMATCH(a, b) (a == '(' && b == ')' || a == '{' && b == '}' || a == '[' && b == ']')
#define MAXSIZE 10000
struct stack {
   
    char data[MAXSIZE/2 + 1];
    int top;
};
// 输入s [1,10000]
bool isValid(char *s) 
{
   
    struct stack st;
    st.top = -1;
    while (*s){
   
        if (ISLEFT(*s) && st.top < MAXSIZE) //输入为左括号,且小于栈的大小
            st.data[++st.top] = *s;
        else if (st.top != -1 && ISMATCH(st.data[st.top], *s)) //输入右括号且与栈顶匹配
            st.top--;
        else //在字符串未读完的情况下,不可能匹配成功。
            return false; //break;
        s++;
    }
    return st.top == -1; //栈清空,字符串全匹配
}

21. 合并两个有序链表
//方法:依次比较两个链表的头,指向值小的链表,该链表指针往后移一位,直到有个链表遍历完。
#include <stdio.h>
#include <stdlib.h>
struct ListNode {
   
    int val;
    struct ListNode *next;
};

struct ListNode *mergeTwoLists(struct ListNode *l1, struct ListNode *l2)
{
   
    //增加头节点,不需要单独处理开始节点。
    struct ListNode *head = (struct ListNode *)malloc(sizeof(struct ListNode));
    head->next = NULL;
    struct ListNode *p = head;

    while (l1 && l2){
   
        if (l1->val > l2->val){
   
            p->next = l2;
            l2 = l2->next;
        }else{
   
            p->next = l1;
            l1 = l1->next;
        }
        p = p->next;
    }

    if (l1)
        p->next = l1;
    else
        p->next = l2;
    
    p=head->next;
    free(head);
    return p;
}

//递归方法
struct ListNode *mergeTwoLists(struct ListNode *l1, struct ListNode *l2)
{
   
    if (!l1)
        return l2;
    if (!l2)
        return l1;
    if (l1->val < l2->val){
    //当前l1小,则头应该为l1,让接下来的l1和l2比较。
        l1->next = mergeTwoLists(l1->next, l2);
        return l1;
    }
    l2->next = mergeTwoLists(l1, l2->next);
    return l2;
}
/*
//根据数组创建无头链表
struct ListNode *creat(int *array, int size)
{
    struct ListNode *q, *head = NULL;
    int i = 0;
    while (i < size){
        struct ListNode *p = (struct ListNode *)malloc(sizeof(struct ListNode));
        p->val = array[i++];
        p->next = NULL;
        if (!head)
            head = p, q = p;
        else
            q->next = p;
        q = p;
    }
    return head;
}

void print(struct ListNode *p)
{
    printf("p=[");
    while (p){
        printf("%d,", p->val);
        p = p->next;
    }
    printf("]\n");
}

int main()
{
    freopen("data.txt", "r", stdin);
    struct ListNode *p[2];
    int N[2];
    //vscode的launch.json中的cwd不是当前工作目录,"D:\\mingw64\\bin",使得freopen("data.txt","r",stdin)出错。使用gcc test.c;a.exe却不会出错。
    while (scanf("%d %d",&N[0],&N[1]) != EOF) {
        int arr[2][30];
        for (int i = 0; i < 2; ++i){
            int j = 0;
            while (j < N[i])
                scanf("%d", &arr[i][j++]);
            p[i] = creat(arr[i], N[i]);
            print(p[i]);
        }
        print(mergeTwoLists(p[0], p[1]));
    }
    fclose(stdin);
    return 0;
}*/
22. 括号生成
//方法:只有当当前字符串满足(左括号数量>=右括号数量),才有可能有效,当左右括号都用完了就得到一条有效字符串。
void generate_str(int i, int j, int *k, int n, char *stack, char **result)
{
   
    if(i<j) return;//当前字符串,左括号数量少于右括号数量,则这个字符串肯定不满足要求;
    if (i < n){
   
        stack[i + j] = '(';
        generate_str(i + 1, j, k, n, stack, result);
    }
    if (j < n){
    //if(i<j)可以合并进来,if(i>j) 表示当左括号多余右括号,才可以添加右括号。
        stack[i + j] = ')';
        generate_str(i, j + 1, k, n, stack, result);
    }
    if (i == j&&i==n){
    //所有括号添加完毕,
        char *s = (char *)malloc(sizeof(char) * n * 2 + 1);
        strcpy(s, stack);
        result[*k] = s;
        *k = *k + 1;
    }
}

char **generateParenthesis(int n, int *returnSize)
{
   
    int left = 0, right = 0;
    char *s = (char *)malloc(sizeof(char) * n * 2 + 1);
    char **result = (char **)malloc(sizeof(char *) * n);
    s[n * 2] = 0;
    generate_str(0, 0, returnSize, n, s, result);
    free(s);
    return result;
}
23. 合并K个升序链表
//方法:由各个链表的当前指针构成最小堆,然后在推根节点的链表指向下一位(不为空)或用堆最后一个节点的链表代替(为空),然后调整堆。直到堆为空。
//还可以用桶排序。分而治之,递归,依次合并两个
#define MAXSIZE 10000
#define MAXVALUE 10001
//调整第i个节点为根的子堆,
void heapAdjust(struct ListNode** heap, int size, int i)
{
   
    heap[0] = heap[i];
    int son = i * 2;
    while (son <= size) {
   
        if (son < size && heap[son + 1]->val < heap[son]->val) //比较两个子节点的大小
            son++;
        if (heap[0]->val > heap[son]->val) {
    //比较最小子节点和父节点的大小
            heap[son / 2] = heap[son];
            son *= 2;
        } else //子树满足最小堆的要求
            break;
    }
    heap[son / 2] = heap[0];
}

struct ListNode* mergeKLists(struct ListNode** lists, int listsSize)
{
   
    //int *p[10] 指针数组,10个int指针构成的数组,int a[3][10]; p[1]=a[1],p++指向a[1][1]
    //int (*p)[10] 数组指针,指向数组长度为10的指针,p=a;p++指向a[1]
    struct ListNode* heap[MAXSIZE + 1] = {
    NULL }; //初始化指针,
    struct ListNode* head = (struct ListNode*)malloc(sizeof(struct ListNode));
    struct ListNode* p = head;
    int size = 0;
    for (int i = 0; i < listsSize; ++i) {
   
        if (lists[i])
            heap[++size] = lists[i];
    }
    //从有子节点的点开始建最小堆
    for (int i = size / 2; i > 0; --i)
        heapAdjust(heap, size, i);

    while (size > 1) {
   
        p->next = heap[1];
        p = heap[1];
        if (!heap[1]->next) {
    //链表遍历完,由堆里最后一个链表替代。堆大小减一
            heap[1] = heap[size];
            size--;
        } else
            heap[1] = heap[1]->next;
        heapAdjust(heap, size, 1);
    }
    p->next = heap[1];
    p = head->next;
    free(head);
    return p;
}
24. 两两交换链表中的节点
//方法:先前指针q、当前指针p、下一个指针p->next,修改三者的指向即可。如1-2-3-4
struct ListNode* swapPairs(struct ListNode* head)
{
   
    struct ListNode* p = head;
    struct ListNode* q = (struct ListNode*)malloc(sizeof(struct ListNode));
    q->next = head;
    head = q;
    while (p) {
   
        if (p->next) {
    //head(q)-1(p)-2-3-4
            q->next = p->next; //head(q)-2-3-4  1(p)-2-3-4
            p->next = p->next->next; //1(p)-3-4  head(q)-2-3-4
            q->next->next = p; //head(q)-2-1(p)-3-4
            q = p;
            p = p->next;
        } else
            break;
    }
    p = head->next;
    free(head);
    return p;
}
25. K 个一组翻转链表
//方法:之前指针、当前指针,边遍历边反转。当满足k步后,连接上一段和该段;不满k步,再反转回去。
//另外可以先得长度为k的链表,再进行反转。
struct ListNode* reverseKGroup(struct ListNode* head, int k)
{
   
    if (k < 2 || !head || !head->next)
        return head;
    struct ListNode* p = (struct ListNode*)malloc(sizeof(struct ListNode));
    p->next = head;
    struct ListNode* start = p;
    while (head) {
   
        struct ListNode *pre = NULL, *begin = head;
        int i = 0;
        while (i < k && head){
    // 每前进一步都进行反转
            struct ListNode* tmp = head->next;
            head->next = pre;
            pre = head;
            head = tmp;
            ++i;
        }
        if (i == k) {
    //长度满足,更新前起始节点
            start->next = pre;
            start = begin;
        } else if (!head) {
    //长度不满足,再次反转最后一段。
            head = pre;
            pre = NULL;
            while (head) {
   
                struct ListNode* tmp = head->next;
                head->next = pre;
                pre = head;
                head = tmp;
            }
            start->next = pre;
        }
    }
    head = p->next;
    free(p);
    return head;
}
30. 串联所有单词的子串
class Solution {
public:
    vector<int> findSubstring(string s, vector<string>& words) {
        unordered_map<string, vector<int>> wordsSet;
        int num = 0;
        for (auto w : words){ //统计各词的出现次数,并将每个词对应一个整数
            if (wordsSet.find(w) != wordsSet.end())
                wordsSet[w][0]++;
            else
                wordsSet[w] = {1, num++};
        }
        int n = s.size(), m = words[0].size(), k = words.size();
        vector<int> vis(num), ans; //vis每个词出现的次数

        for (int i = 0; i <= n - m * k; ++i){
            auto sub = s.substr(i, m);
            if (wordsSet.find(sub) != wordsSet.end()){
                for (auto w : wordsSet)
                    vis[w.second[1]] = w.second[0];
                int cnt = k, j = i;
                while (wordsSet.find(sub) != wordsSet.end() && vis[wordsSet[sub][1]] > 0){
                    vis[wordsSet[sub][1]]--;
                    j = j + m;
                    sub = s.substr(j, m);
                    cnt--;
                }
                if (cnt == 0)//所有词都出现
                    ans.push_back(i);
            }
        }
        return ans;
    }
};
//还可以从第一个单词的每个位置开始,一次移动每个单词的长度,这样在后续比较时,可以跳过一些不可能匹配的子串。
32. 最长有效括号
//记录当前还有多少个左括号等待匹配,第i个位置上的值表示当前已经匹配的括号数。长度的更新只发生在出现右括号的时候
class Solution {
public:
    int longestValidParentheses(string s) {
        int i = 0, n = s.size(), st = 0;
        vector<int> preAns(n, 0);//存储已经匹配了括号数
        int maxLen = 0;
        for (int i = 0; i < n; ++i){
            if (s[i] == ')'){
                if (st == 0){
                    preAns[0] = 0;//没有左括号匹配,重新开始
                } else {  //有匹配的括号
                    preAns[st - 1] += preAns[st] + 2; //之前已经匹配的个数+后面匹配的个数+最新匹配的两个
                    preAns[st--] = 0;
                    maxLen = fmax(maxLen, preAns[st]);
                }
            }
            else
                st++;
        }
        return maxLen;
    }
};
//leetcode的方法更简洁;可以指比较左右括号的个数,右括号多,则一定不满足,相等则一定匹配,为了求左括号多的情况,反向遍历
37. 解数独
class Solution {
    const int n = 9;
    int m;
    vector<vector<bool>> vis;  //vector<vector<bool>> vis(27,vector<bool>(10,false));会出错
    vector<pair<int, int>> arr;
    bool dfs(vector<vector<char>> &board, int ii){
        if (ii == m)
            return true;
        int i = arr[ii].first, j = arr[ii].second;
        for (int k = 1; k < 10; ++k){
            int x = i / 3, y = j / 3;
            int i1 = x * 3 + y + 18, i2 = i, i3 = j + 9;
            if (!vis[i1][k] && !vis[i2][k] && !vis[i3][k]){
                vis[i1][k] = 1, vis[i2][k] = 1, vis[i3][k] = 1;
                board[i][j] = '0' + k;
                if (dfs(board, ii + 1))
                    return true;
                vis[i1][k] = 0, vis[i2][k] = 0, vis[i3][k] = 0;
            }
        }
        return false;
    }

public:
    void solveSudoku(vector<vector<char>>& board) {
        vis.assign(27, vector<bool>(10, false));
        for (int i = 0; i < n; ++i){
            for (int j = 0; j < n; ++j){
                if (board[i][j] != '.'){
                    int x = i / 3, y = j / 3, k = board[i][j] - '0';
                    vis[x * 3 + y + 18][k] = 1;
                    vis[i][k] = 1;
                    vis[j + 9][k] = 1;
                }
                else
                    arr.emplace_back(i, j); //emplace_back()原地构造,不需要触发拷贝构造和转移构造。
            }
        }
        m = arr.size();
        dfs(board, 0);
    }
};
41. 缺失的第一个正数
//在原数组上修改。用来记录某个数是否出现过。
class Solution {
public:
    int firstMissingPositive(vector<int>& nums) {
        int MinInt = 1, n = nums.size();
        for (int i = 0; i < n; ++i){
            if (nums[i] < 1)
                nums[i] = (n + 1);
        }
        for (int i = 0; i < n; ++i){
            int k = abs(nums[i]) - 1;
            if (k < n)
                nums[k] = -abs(nums[k]);
        }
        for (int i = 0; i < n; ++i){
            if (nums[i] > 0)
                return i + 1;
        }
        return n + 1;
    }
};
48. 旋转图像
//围绕中心点逐步旋转
class Solution {
public:
    void rotate(vector<vector<int>>& matrix) {
        int n=matrix.size();
        for(int i=0;i<n/2;++i){//由外往里的层数
            for(int j=i;j<n-i-1;++j){//处理每一层
                int t=matrix[i][j];
                matrix[i][j]=matrix[n-j-1][i];
                matrix[n-j-1][i]=matrix[n-i-1][n-j-1];
                matrix[n-i-1][n-j-1]=matrix[j][n-i-1];
                matrix[j][n-i-1]=t;
            }
        }
    }
};
//可以先水平翻转,再对角线翻转
92. 反转链表 II
class Solution {
public:
    ListNode *reverseBetween(ListNode *head, int left, int right) {
        if (left == right)
            return head;
        int i = 1;
        ListNode h(0, head);
        ListNode *p = head, *p_l = &h, *pre = &h;
        while (i < left) {  //找到left对应的节点p
            pre = p;
            p = p->next;
            i++;
        }
        p_l = pre;//不进行翻转的前一个节点
        ListNode *p_r = p;//进行翻转的第一个节点,
        pre = p, p = p->next, i++;
        while (i <= right) { //需要翻转的节点
            ListNode *q = p->next;
            p->next = pre;
            pre = p, p = q;
            i++;
        }
        p_l->next = pre, p_r->next = p;//将left前面的链表、翻转链表、right后面的链表 连接起来

        return h.next;
    }
};
/*
for (int i = 0; i < right - left; i++) {
    next = cur->next;
    cur->next = next->next;
    next->next = pre->next;
    pre->next = next;
}
*/
115. 不同的子序列
//可以进一步优化,只需要两行的dp,存储上一行和当前行;dp[i][j]表示字符串s的[:,i]里匹配t[:j]的子序列个数
class Solution {
public:
    int numDistinct(string s, string t) {
        int n = s.size(), m = t.size();f
        if (n < m)
            return 0;
        vector<vector<long>> dp(2, vector<long>(n + 1, 0));
        for (int i = 0; i <= n; ++i)
            dp[0][i] = 1;
        int row, prerow = 0;
        for (int i = 1; i <= m; i++) {
            row = i & 1;
            dp[row][i - 1] = 0;
            for (int j = i; j <= n; j++) {
                cout << "(" << t[i - 1] << "," << s[j - 1] << "),";
                if (t[i - 1] == s[j - 1]) { //i-1和j-1新匹配个数+之前已经匹配的个数
                    dp[row][j] = dp[row][j - 1] + dp[prerow][j - 1]; 
                } else { //不产生新匹配的子序列,
                    dp[row][j] = dp[row][j - 1];
                }
            }
            prerow = row;
        }
        return dp[row][n];
    }
};
119. 杨辉三角 II
//方法:杨辉三角为二项式(两个数之和的整数次幂)的系数,有规律,组合。
//也可以一层一层的算。
int *getRow(int rowIndex, int *returnSize)
{
   
    int *ans = (int *)malloc(sizeof(int) * (rowIndex + 1));
    ans[0] = ans[rowIndex] = 1;
    for (int i = 1; i < rowIndex; ++i){
   
        if (i <= rowIndex / 2)
            ans[i] = (long long)ans[i - 1] * (rowIndex - i + 1) / i; //注意运算可能溢出。
        else
            ans[i] = ans[rowIndex - i];
    }
    *returnSize = rowIndex + 1;
    return ans;
}
124. 二叉树中的最大路径和
class Solution {
    int maxSum = -2000;
    int dfs(TreeNode *root, int sum) {
        if (root == nullptr)
            return 0;
        sum += root->val;
        maxSum = max(sum, maxSum); //某条路径执行到当前点的最大值
        sum = max(sum, 0);
        int leftSum = dfs(root->left, sum);
        leftSum += root->val;
        leftSum = max(0, leftSum); 
        sum = max(sum, leftSum); 左边子树某点出发经过该点
        int rightSum = dfs(root->right, sum);
        rightSum += root->val;
        rightSum = max(0, rightSum);
        return max(rightSum, leftSum); //经过该点返回的最大值
    }

public:
    int maxPathSum(TreeNode *root) {
        dfs(root, 0);
        return maxSum;
    }
};
/*更简洁的版本
int dfs(TreeNode *root) {
    if (root == nullptr)
        return 0;
    int leftSum = dfs(root->left);
    int rightSum = dfs(root->right);
    leftSum = max(leftSum, 0);
    rightSum = max(rightSum, 0);
    int sum = leftSum + rightSum + root->val;
    maxSum = max(sum, maxSum);
    return max(leftSum, rightSum) + root->val;
}*/
131. 分割回文串
//使用递归,不断尝试不同长度的分割(从0当前位置到n最后一个位置),还可以将求回文串标志加到搜索里面。
//记忆化搜索,需要另一个函数来求标志的值,会出现递归调用的情况,当复杂度一样的,
class Solution{
    vector<string> tmp;
    vector<vector<bool>> flag; //位置i到j是否为回文串。
    vector<vector<string>> ans;
    int n;
    void dfs(int i, string &s){
        if (i == n){
            ans.push_back(tmp);
            return;
        }
        for (int j = i; j < n; ++j){
            if (flag[i][j]){
                tmp.push_back(s.substr(i, j - i + 1));
                dfs(j + 1, s);
                tmp.pop_back();
            }
        }
    }

public:
    vector<vector<string>> partition(string s){
        n = s.size();
        flag.assign(n, vector<bool>(n, true));
        //因为我们会用到ij中间(i+1到j-1)的字符串是否匹配。所以必须先求之间是否回文。三角形范围从小到大
        for (int i = n - 1; i >= 0; --i){ //矩阵的右下角
            for (int j = i + 1; j < n; ++j)
                flag[i][j] = (s[i] == s[j]) && flag[i + 1][j - 1];
        }
        //for (int i = 0; i <n ; ++i){ //矩阵的左下角
        //    for (int j = i-1; j >=0; --j)
        //        flag[i][j] = (s[i] == s[j]) && flag[i - 1][j + 1];
        //}
        dfs(0, s);
        return ans;
    }
};
132. 分割回文串 II
//同上,使用相同的方法求所有i到j是否为回文串。然后使用DP[i]表示0-i分割的最小次数,由0-i是否为回文串,DP[i]=min{DP[j]}+1(不是)或0(是);
//从左往右依次新加一个字符s[i],求DP[i],之前位置上DP已经求得,新加的s[i]可能使得DP[i]=min{DP[j]}+1;
class Solution {
public:
    int minCut(string s){
        int n = s.size();
        vector<vector<bool>> f(n, vector<bool>(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];
            }
        }
        vector<int> ans(n, INT_MAX);
        for (int i = 0; i < n; ++i){
            if (f[0][i]){
                ans[i] = 0;
                continue;
            }
            for (int j = 0; j < i; ++j){
                if (f[j + 1][i])
                    ans[i] = min(ans[i], ans[j] + 1);
            }
        }
        return ans[n - 1];
    }
};
149. 直线上最多的点数
//对于节点i,我们只需统计i+1到n的节点与i构成的线,取点数最大值的线。不需要考虑i之前的点,因为之前通过i和之前的点的线已经考虑过了。
//新加一个点i,需要遍历之前节点,确定到线,线上点数加一,注意:去重。
class Solution {
    int n;
    int max_lines_contains_i(vector<vector<int>> &points, int i) {//求在一条线上的点数最大的值。
        unordered_map<double, int> line;
        int duplicate = 0, verti = 1;
        int maxp = 0;
        int x1 = points[i][0], y1 = points[i][1];
        for (int j = i + 1; j < n; ++j) {
            int x2 = points[j][0], y2 = points[j][1];
            if (x1 == x2 && y1 == y2)
                duplicate++;
            else if (x1 == x2) {
                verti++;
            } else {
                double scope = 1.0 * (y1 - y2) / (x1 - x2);
                if (line.count(scope))
                    line[scope]++;
                else
                    line[scope] = 2;
                maxp = max(line[scope], maxp);
            }
        }
        return max(maxp, verti) + duplicate;
    }

public:
    int maxPoints(vector<vector<int>> &points) {
        n = points.size();
        if (n < 3)
            return n;
        int maxp = 1;
        for (int i = 0; i < n - 1; ++i) {
            maxp = max(max_lines_contains_i(points, i), maxp);
        }
        return maxp;
    }
};
150. 逆波兰表达式求值
//没有考虑单操作数,如3-(-(1+1)) 3 1 1 + - - 当作非法输入
class Solution {
public:
	int evalRPN(vector<string>& tokens) {
		stack<int>num;
		for (auto s : tokens) {
			if (isdigit(s[s.size() - 1])) {//数可能为负数
				int n = stoi(s);
				num.push(n);
			}
			else {
				int n1, n2, x;
				n2 = num.top(), num.pop();
				n1 = num.top(), num.pop(); //后弹出的为前一个操作数
				if (s == "+")x = n1 + n2;
				else if (s == "-")x = n1 - n2;
				else if (s == "*")x = n1 * n2;
				else x = n1 / n2;
				num.push(x);
			}
		}
		return num.top();
	}
};
224. 基本计算器
//官方代码更简洁
class Solution{
public:
    int calculate(string s){
        int n = s.size();
        int localSum = 0;
        stack<int> num;
        stack<char> ope;
        for (int i = 0; i < n; ++i){
            if (isdigit(s[i])){
                int k = 0;
                while (isdigit(s[i])){
                    k = k * 10 + (s[i] - '0'); //加括号防止超出int类型范围
                    i++;
                }
                if (ope.empty() || ope.top() == '(')//前面没有操作符
                    localSum += k;
                else {                     //前面有操作符,需要删掉操作符
                    if (ope.top() == '+') 
                        localSum += k;
                    else if (ope.top() == '-')
                        localSum -= k;
                    ope.pop();
                }
            }
            if (s[i] == ' ')
                continue;
            if (s[i] == '(') { //计算新的子表达式结构,存储之前的和
                num.push(localSum);
                localSum = 0;
                ope.push('(');
            }
            else if (s[i] == ')'){ //子表达式计算结束
                int k = localSum;
                localSum = num.top();
                while (!ope.empty() && ope.top() == '(') //弹出所有出结果了的子表达式
                    ope.pop();
                if (ope.empty() || ope.top() == '+')
                    localSum += k;
                else if (ope.top() == '-')
                    localSum -= k;
                if (!ope.empty()) //子表达式为总表达式
                    ope.pop();
                num.pop();
            }
            else //压入操作符符号
                ope.push(s[i]);
        }
        return localSum;
    }
};

227. 基本计算器 II
//无括号的计算器,使用双栈
class Solution{
    stack<int> num;
    stack<char> opt;
    int compute(){
        int p = num.top();num.pop();
        int q = num.top();num.pop();
        char c = opt.top();opt.pop();
        if (c == '*')
            return q * p;
        else if (c == '/')
            return q / p;
        else if (c == '+')
            return q + p;
        return q - p;
    }

public:
    int calculate(string s){
        int n = s.size(), i = 0, ans = 0;
        unordered_map<char, int> pri{
  {'*', 1}, {'/', 1}, {'+', 0}, {'-', 0}};
        //pri['*']=1,pri['/']=1,pri['+']=0,pri['-']=0;
        while (i < n){
            if (s[i] == ' ')
                i++;
            else if (!isdigit(s[i])){
                while (!opt.empty() && pri[s[i]] <= pri[opt.top()])
                    num.push(compute());
                opt.push(s[i]);
                i++;
            }else{
                int k = 0;
                while (i < n && isdigit(s[i]))
                    k = k * 10 + (s[i++] - '0');
                num.push(k);
            }
        }
        while (!opt.empty())
            num.push(compute());
        return num.top();
    }
};
//还可以用栈存储*和/的计算结果,后面再求总和
232. 用栈实现队列
//一个队列作为压入队栈,一个作为弹出栈,队列当要弹出元素时,如果弹出栈为空,将压入栈的元素压入到弹出栈中。
class MyQueue {
public:
    stack<int> inStack, outStack;
    void in2out(){
        while (!inStack.empty()){
            int x = inStack.top();
            outStack.push(x);
            inStack.pop();
        }
    }
    /** Initialize your data structure here. */
    MyQueue() {}

    /** Push element x to the back of queue. */
    void push(int x){
        inStack.push(x);
    }
    /** Removes the element from in front of queue and returns that element. */
    int pop(){
        if (outStack.empty())
            in2out();
        int x = outStack.top();
        outStack.pop();
        return x;
    }
    /** Get the front element. */
    int peek(){
        if (outStack.empty())
            in2out();
        return outStack.top();
    }
    /** Returns whether the queue is empty. */
    bool empty(){
        return inStack.empty() && outStack.empty();
    }
};
300. 最长递增子序列
class Solution {
public:
    int lengthOfLIS(vector<int> &nums) {
        int n = nums.size(), maxLen = 1;
        vector<int> dp(n, 1);
        for (int i = 1; i < n; ++i) {
            for (int j = 0; j < i; ++j) {
                if (nums[i] > nums[j]) {
                    dp[i] = max(dp[i], dp[j] + 1);
                    maxLen = max(maxLen, dp[i]);
                }
            }
        }
        return maxLen;
    }
};
331. 验证二叉树的前序序列化
//统计树最少未遍历的节点数cnt,遍历一个新节点,cnt--;遇到数字:cnt+=2;
class Solution {
public:
    bool isValidSerialization(string preorder){
        int n = preorder.size(), i = 0, cnt = 1;
        while (i < n){
            if (preorder[i] == ',')
                i++;
            else{
                if (!cnt)
                    return false;
                cnt--;
                if (preorder[i] != '#')
                    cnt += 2;
                while (i < n && preorder[i] != ',')
                    i++;
            }
        }
        return cnt == 0;
    }
};
354. 俄罗斯套娃信封问题
//方法:放的方法为:根据大小,对于长和宽都满足要求的(都大于前一封),依次放;如果只有一条边满足要求,放最小的且满足要求的;
//问题:我们不应该放某条过大的边,因为这样会导致后面本来更多满足要求信封无法放入。所以对一条边排好序后,再对另一条边排序,为了放入更多的信封,我们可能会舍弃掉之前的信封。
class Solution{
public:
    int maxEnvelopes(vector<vector<int>> &envelopes) {
        sort(envelopes.begin(), envelopes.end(), [](const auto &e1, const auto &e2) {
            return e1[0] < e2[0] || (e1[0] == e2[0] && e1[1] > e2[1]);
        });
        int n = envelopes.size();
        /*//错误,没有考虑中间某个信封过大,导致后面本可以放进的信封无法放入。
        auto pre=envelopes[0];
        auto f=[](const auto& e1,const auto& e2){return e1[0]<e2[0] && e1[1]<e2[1];};
        int cnt=1;
        for(int i=1;i<n;++i){
            if(f(pre,envelopes[i])){
                cnt++;
                pre=envelopes[i];
                //cout<<pre[0]<<","<<pre[1]<<"->";
            }
        }
        return cnt;
        */
        //动态规划,用dp[i]存储当前可以放入i的最大信封数,即所有小于信封i的 信封k的位置的 值的最大值+1;
        vector<int> dp(n, 1);
        for (int i = 0; i < n; ++i){
            for (int j = 0; j < i; ++j){
                if (envelopes[i][1] > envelopes[j][1] && dp[i] < dp[j] + 1) //信封j后能放下i,
                    dp[i] = dp[j] + 1;
            }
        }
        return *max_element(dp.begin(), dp.end());
        /*方法二:用f来存储信封应该在的位置,使得从0到该位置的信封满足要求,递增。0到当前的长度 表示 包含当前位置上的信封 所构成的最大信封数。
        vector<int> f = {envelopes[0][1]};
        for (int i = 0; i < n; ++i){
            int num = envelopes[i][1];
            if (num > f.back())
                f.push_back(num);
            else{
                auto it = lower_bound(f.begin(), f.end(), num);
                *it = num;
            }
        }
        return f.size();*/
    }
};
420. 强密码检验器
struct cmp {
    bool operator()(const int a, const int b) { return a % 3 > b % 3; }
};

class Solution {
public:
    int strongPasswordChecker(string password) {
        int n = password.size();
        char pre;
        vector<int> rep;
        int digit = -1, lower = -1, upper = -1, dup = 0;
        for (int i = 0; i < n; ++i) {
            if (i > 0 && pre == password[i])
                dup++;
            else {
                if (dup > 2)
                    rep.push_back(dup);
                dup = 1;
            }
            if (isdigit(password[i]))
                digit++;
            else if (password[i] >= 'A' && password[i] <= 'Z')
                upper++;
            else if (password[i] >= 'a' && password[i] <= 'z')
                lower++;
            pre = password[i];
        }
        int ans = 0;
        if (dup > 2)
            rep.push_back(dup);
        if (upper > -1 && digit > -1 && lower > -1 && rep.empty() && n > 5 &&
            n < 21)
            return 0;
        int off = 0, repcnt = 0, change = 0;
        digit = digit > -1 ? 0 : 1;
        lower = lower > -1 ? 0 : 1;
        upper = upper > -1 ? 0 : 1;
        change = digit + lower + upper;
        for (int i = 0; i < rep.size(); ++i)
            repcnt += rep[i] / 3;
        if (n < 6) {
            off = 6 - n;
            while (off > 0 || change > 0 || repcnt > 0)
                off--, change--, repcnt--, ans++;
        } else if (n <= 20) {
            ans = max(repcnt, change);
        } else {
            off = n - 20;
            priority_queue<int, vector<int>, cmp> que(rep.begin(), rep.end());
            while (off > 0 && !que.empty()) {
                int x = que.top();
                que.pop();
                if (x / 3 - (x - 1) / 3)
                    repcnt--;
                x--, off--, ans++;
                if (x > 2)
                    que.push(x);
            }
            while (off > 0)
                off--, ans++;
            while (repcnt > 0 || change > 0)
                change--, repcnt--, ans++;
        }
        return ans;
    }
};
424. 替换后的最长重复字符
//方法:滑动窗口,要求的字符串是连续的,替换的字符是子串中出现次数较少的字符,且这些字符的总数不大于k。
//给定一个窗口,找里面字符出现最多的字符作为重复字符。窗口长度-其个数<=k,所以只有最大出现次数变大,窗口才可能变大。
//依次往窗口里添加字符,在满足要求的情况下,窗口不断增大,当不满足时,因为是连续的,去掉最左边。
int characterReplacement(char* s, int k)
{
    int num[26]; //满足要求的区间内各字符的个数
    memset(num, 0, sizeof(num));
    int n = strlen(s);
    int maxn = 0;
    int left = 0, right = 0;
    while (right < n) {
        num[s[right] - 'A']++; //字符数加一
        maxn = max(maxn, num[s[right] - 'A']); //区间内出现的最大字符个数
        if (right - left + 1 - maxn > k) {      //区间内需要替换的字符个数>最大替换次数,不满足要求,去掉最左边的字符。
            num[s[left] - 'A']--;               //则左边字符的个数减一
            left++;
        }
        right++;
    }
    return right - left;
}
448. 找到所有数组中消失的数字
//方法:基本方法是申请一个大小为n的数组,用来标记下标为i的数是否出现。因为值全部小于n,所以可以利用原数组来表示,如果下标为i的数出现,则i位置上的数就加n。
//或者可以进行排序,排第i个位置时,往后查找为值为i的地址,并交互,则缺失的数会找不到。
int *findDisappearedNumbers(int *nums, int numsSize, int *returnSize)
{
   
    for (int i = 0; i < numsSize; ++i)
        nums[(nums[i] - 1) % numsSize] += numsSize;
    int *ans = (int *)malloc(sizeof(int) * numsSize);
    *returnSize = 0;
    for (int i = 0; i < numsSize; ++i){
   
        if (nums[i] <= numsSize)
            ans[(*returnSize)++] = i + 1;
    }
    return ans;
}
480. 滑动窗口中位数
//方法:构建两个堆,一个最大堆存储前半部分,一个最小堆存储后半部分,每次只需比较两个堆顶的值和新加入的值,并删掉窗口去掉的值,然后不断维护两个堆。
//窗口长度为奇数时,最大堆存储的个数比最小堆多一个,则大堆堆顶的值即为中位数。
//leetcode使用了优先队列和延迟删除,主要堆顶的元素不是需要删除的元素,则其作为中位数就不会出错,相当于两个队列将窗口同时往两边扩大。
#define MAXSIZE 100000
typedef struct node {
   
    int i, v;
} node;

void maxHeapPush(node* heap, int* s, node k)
{
   
    int son 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值
>