Leetcode题库(21-40)

Leetcode21. 合并两个有序链表

思路: 归并排序中归并的思想,对于两个有序队列而言,每次取他们最小值添加到答案队列最大值位置即可排序。
代码:

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };
 */
class Solution {
public:
    ListNode* mergeTwoLists(ListNode* list1, ListNode* list2) {
        ListNode* result = new ListNode(), *now;  // result为返回答案(方便起见初始化为开头为0的一个链表,后续返回他的指向就行), now为每次用于生成新ListNode的变量
        ListNode* ed = result;  // ed指向链表末尾
        while(list1 != nullptr && list2 != nullptr){
        	// 将两个队列中小的取出
            if(list1->val < list2->val) now = new ListNode(list1->val), list1 = list1->next;
            else now = new ListNode(list2->val), list2 = list2->next;
            ed->next = now, ed = now;  // 将list1,list2的最小值放在队列最大值位置, 更新ed指向
        }
        while(list1 != nullptr){  // 处理list1不为空情况, 直接添加到答案队列就行
            now = new ListNode(list1->val), list1 = list1->next;
            ed->next = now, ed = now;
        }
        while(list2 != nullptr){  // 处理list2不为空情况, 直接添加到答案队列就行
            now = new ListNode(list2->val), list2 = list2->next;
            ed->next = now, ed = now;
        }
        return result->next;  // 因为初始化为一个开头为0的队列, 所以删除开头的0后返回
    }
};



Leetcode22. 括号生成

思路: 对于一个合法的括号序列而言,当前括号序列必然可以拆分成三个部分:与最左侧括号匹配的右括号,这对括号中合法的括号序列,这对括号后合法的序列(例如:‘(()())()’,可以拆分为位置在0和5的括号对 ‘(’ ‘)’,这对括号中的合法括号序列 ‘()()’,这对括号后的合法序列 ‘()’)。那么一个有 n n n对括号的括号序列可以看做如下情况{ ‘(’ + 有 l l l对括号的合法括号序列 + ‘)’ + 有 n − l − 1 n-l-1 nl1对括号的合法括号序列’ }。那么对于当前答案而言,就是对于 l l l对括号的合法括号序列 n − l − 1 n-l-1 nl1对括号的合法括号序列的组合,由于 l l l n − l − 1 n-l-1 nl1都必然小于 n n n,所以当前生成的括号集合可以用以前获得的括号集合生成。
代码:

class Solution {
public:
    vector<string> generateParenthesis(int n) {
        vector<string> ans[10];
        ans[0].push_back("");  // 初始化长度为0的括号序列
        ans[1].push_back("()");    // 初始化长度为1的括号序列
        for(int i = 2;i <= n;i++){  // 当前求的总括号对数
            for(int l = 0;l < i;l++){  // 括号中间合法括号串的括号对数
                int r = i - l - 1;  // 右合法括号串的括号对数
                for(int j = 0;j < ans[l].size();j++){
                    for(int k = 0;k < ans[r].size();k++){
                        ans[i].push_back("(" + ans[r][k] + ")" + ans[l][j]);  // 将中间和后面的括号集合进行组合获得当前长度的合法串
                    }
                }
            }
        }
        return ans[n];
    }
};



Leetcode23. 合并 K 个升序链表

思路: 与21题一样,都是归并排序中归并的思想。每次选所有升序链表中最小值添加到答案的最大位置,不过由于每次都要遍历所有链表,所以这个算法时间复杂度为 O ( K ∗ N ) O(K*N) O(KN) K K K为链表个数, N N N为链表中数字总个数。(当然也可以直接遍历一遍所有数字,把他们都扔到一个最小堆里,时间复杂度是 O ( N l o g ( N ) ) O(Nlog(N)) O(Nlog(N))
代码1(归并):

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };
 */
class Solution {
public:
    ListNode* mergeKLists(vector<ListNode*>& lists) {
        ListNode* result = new ListNode(), *now;  // result为返回答案(方便起见初始化为开头为0的一个链表,后续返回他的指向就行), now为每次用于生成新ListNode的变量
        ListNode* ed = result;  // ed指向链表末尾
        int mn;  // 指向lists中最小数字最小的串
        do{
            mn = -1;  // 每次初始化为-1, 即不存在
            for(int i = 0;i < lists.size();i++){
                if(lists[i] != nullptr){
                    if(mn == -1 || lists[i]->val < lists[mn]->val) mn = i;  // 更新mn
                }
            }
            if(mn != -1){
                now = new ListNode(lists[mn]->val);
                ed->next = now, ed = now;  // 最小值放在队列最大值位置, 更新ed指向
                lists[mn] = lists[mn]->next;
            }
        }while(mn != -1);  // 如果没找到说明已经取出所有数字了
        return result->next;
    }
};

代码2(扔小顶堆):

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };
 */
class Solution {
public:
    ListNode* mergeKLists(vector<ListNode*>& lists) {
        ListNode* result = new ListNode(), *now;
        ListNode* ed = result;
        priority_queue<int, vector<int>, greater<int> > qu;
        while(!qu.empty()) qu.pop();
        for(int i = 0;i < lists.size();i++){  // 获得所有数字
            while(lists[i] != nullptr) qu.push(lists[i]->val), lists[i] = lists[i]->next;
        }
        while(!qu.empty()){  // 输出所有数字
            now = new ListNode(qu.top()), qu.pop();
            ed->next = now, ed = now;
        }
        return result->next;
    }
};



Leetcode24. 两两交换链表中的节点

思路: 按题意模拟即可。
代码:

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };
 */
class Solution {
public:
    ListNode* swapPairs(ListNode* head) {
        if(head == nullptr) return head;  // 特判初始为空的情况
        ListNode* result = new ListNode(0, head);  // 初始化答案指针
        ListNode* headPre = result, *headAft = head->next;  // headPre:head前一个位置的指针, headAft:head后一个位置的指针, 当前要交换的两个数字左边那个为head
        while(head != nullptr && headAft != nullptr){
            head->next = headAft->next;  // head后一个指向headAft的后一个
            headAft->next = head;  // headAft的后一个指向head
            headPre->next = headAft;  // headPre的后一个指向headAft
            headPre = head;  // 更新headPre(右移两位)
            head = head->next;  // 更新head(右移两位)
            if(head != nullptr) headAft = head->next;  // 更新headAft(右移两位)
        }
        return result->next;
    }
};



Leetcode25. K 个一组翻转链表

思路: 递归,每K个数据分为一组分别处理。对于每组数据,采用递归的方法,发现如果现在的左指针是右指针的左边一个数,那么将右指针所指的数的next指向左指针。
自己按照思路画个图就清楚了。
代码:

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };
 */
class Solution {
public:
	// l为当前翻转区间的左指针, r为当前翻转区间的右指针
	// 如果当前左指针下一个数字不是右指针所指的数字, 那么递归翻转reverse(l->next, r)
	// 当执行l->next->next = l之前已经确保l->next之后的所有位置都翻转了, 这时候翻转l->next和l即可
    void reverse(ListNode* l, ListNode* r){
        if(l->next != r) reverse(l->next, r);
        l->next->next = l;
    }

    ListNode* reverseKGroup(ListNode* head, int k) {
    	// 特判k==1的时候不用处理
        if(k == 1) return head;
        // 用于存储答案
        ListNode* result = new ListNode(0, head);
        // tmp 当前遍历到的位置, nx 下一区间开头, pre 上一区间尾部位置, ttmp 当前区间开头位置
        ListNode* tmp = head, *nx, *pre = result, *ttmp;
        // 当前区间长度
        int cnt = 0;
        while(tmp != nullptr){
            cnt = cnt + 1;
            // 如果区间长为k
            if(cnt == k){
                cnt = 0;
                nx = tmp->next;  // 下一区间开头
                ttmp = pre->next;  // 当前区间开头
                reverse(pre->next, tmp);  // 翻转长度为K的区间
                ttmp->next = nx;  // 更新当前区间末尾的下一位置
                pre->next = tmp;  // 更新上一区间末尾的下一位置
                tmp = ttmp;  // 更新当前位置
                pre = ttmp;  // 更新上一区间末尾位置
            }
            tmp = tmp->next;
        }
        return result->next;
    }
};



Leetcode26. 删除有序数组中的重复项

思路: 用k代表不重复的数字个数,那么k相当于指向下一个存储位置。原数组已经从小到大排序,所以我们只需要判断当前数据是否与前一个相同,如果不同将其添加到答案位置中。
代码:

class Solution {
public:
    int removeDuplicates(vector<int>& nums) {
        int k = 1;  // 不重复的数字个数
        for(int i = 1;i < nums.size();i++){
            if(nums[i] != nums[i - 1]) nums[k++] = nums[i];
        }
        return k;
    }
};



Leetcode27. 移除元素

思路: 从前往后比较每个数据是否与val相同即可,如果不同添加到答案数组。
代码:

class Solution {
public:
    int removeElement(vector<int>& nums, int val) {
        int k = -1;  // 记录答案的位置下标
        for(int i = 0;i < nums.size();i++){
            if(nums[i] != val) nums[++k] = nums[i];  // 如果不同添加到答案中
        }
        return k + 1;
    }
};



Leetcode28. 找出字符串中第一个匹配项的下标

思路: 两字符串匹配问题可以用KMP(有关KMP的讲解网上很多)。
代码:

class Solution {
public:
	// 对模式串进行处理, 获取next值
    void getNext(int next[], string needle){
        next[0] = -1;
        int i = 0,j = -1;
        while(i < needle.length()){
            if(j == -1 || needle[i] == needle[j]) next[++i] = ++j;
            else j = next[j];
        }
    }
    int strStr(string haystack, string needle) {
        int next[1001];
        memset(next, 0, sizeof(next));
        getNext(next, needle);
        int i = 0,j = 0, hLen = haystack.length(), nLen = needle.length();
        while(i < hLen && j < nLen){  // 对两串进行匹配
            if(j == -1 || haystack[i] == needle[j]) ++i, ++j;  // 如果模式串的指针来到了头部, 或者模式串当前字符与字符串的当前字符相同, 那么向右移动两指针
            else j = next[j];  // 否则将模式串指针移到上一合适位置
        }
        if(j >= needle.length()) return i - nLen;
        return -1;
    }
};



Leetcode29. 两数相除

思路1: 一眼大数除法,然后又注意到输入的是int类型,如果分解为字符串还是需要用到除法。。。那么可以用二分的方法确定商,然后用快速乘的方法用加法模拟乘法。
(写的最心累的一题)
代码1:

class Solution {
public:
	// 标记是否溢出1为溢出
    int overflow = 0;
    // 用加法方式实现乘法, a*b, 传入的时候a必为正数, 将正数加法转为负数加法, 防溢出
    int ksc(int a,int b){
        int ans = 0, flag = b < 0 ? -1 : 1;
        if(!a || !b) return 0;
        if(b < 0) b = -b;
        a = -a;
        while(b){
            if(b & 1){
                if(flag > 0 && -2147483647 - a > ans){  // 正数溢出
                    overflow = 1;
                    return 2147483647;
                }else if(flag < 0 && -2147483648 - a > ans){  // 负数溢出
                    overflow = 1;
                    return -2147483648;
                }
                ans = ans + a;
            }
            b >>= 1;
            if(!b) break;
            if(flag > 0 && -2147483647 - a > a){  // 正数加法因子溢出
                overflow = 1;
                return 2147483647;
            }else if(flag < 0 && -2147483648 - a > a){  // 负数加法因子溢出
                overflow = 1;
                return -2147483648;
            }
            a += a;
        }
        if(flag < 0) return ans;
        if(ans == -2147483648){
            overflow = 1;
            return 2147483647;
        }return -ans;
    }
	// 判断某个数是否为2的幂次
    bool isPow(int x){
        if(x < 0) x = -x;
        int times = 0;
        while(x){
            if(x & 1) ++times;
            x >>= 1;
        }
        if(times > 1) return false;
        return true;
    }

    int divide(int dividend, int divisor) {
    	// 二分左指针, 右指针, 中间值, 当前计算值, 答案
        int l = -1073741824,r = 1073741823,mid,tmp, ans = 0;
        if(divisor == 1) return dividend;  // 除数为1直接输出
        if(dividend == 0) return 0;  // 被除数为0直接输出0
        if(dividend == divisor) return 1;  // 两者相等直接输出1
        if(divisor == -2147483648) return 0;  // 除数为-2147483648值必为0(被除数为-2147483648的情况上面考虑过了)
        if(divisor == -1){  // 输出除数为-1的情况
            if(dividend == -2147483648) return 2147483647;  // 正数溢出
            else return -dividend;
        }
        int add = 0;  // 最后加的数
        // 如果被除数为-2147483648则直接把他+1,如果除数是2的幂次, 那么最后要加一个数
        // 因为-2147483648除以2的幂次的数, 最后答案绝对值比-2147483647除以2的幂次的数绝对值大1
        if(dividend == -2147483648 && isPow(divisor)){
            if(divisor > 0) add = -1;
            else add = 1;
        }
        if(dividend == -2147483648) dividend++;
        // 把除数化为正数
        if (divisor < 0) dividend = -dividend, divisor = -divisor;
        // 二分
        while(l <= r){
            mid = (l + r) >> 1;
            overflow = 0;  // 初始化overflow标记
            tmp = ksc(divisor, mid);  // 计算divisor*mid
            // 分类讨论, 这部分不细说了, 按算法画个图就懂了, 需要注意的是min和max不能乱用
            if(dividend > 0 && tmp <= 0) ans = max(ans, mid), l = mid + 1;
            else if(dividend > 0){
                if(tmp < dividend) ans = mid, l = mid + 1;
                else if(tmp == dividend && !overflow) ans = mid, r = mid - 1;
                else r = mid - 1;
            }else if(dividend < 0 && tmp >= 0) ans == min(ans, tmp), r = mid - 1;
            else{
                if(tmp < dividend) l = mid + 1;
                else if(tmp == dividend) ans = mid, l = mid + 1;
                else if(!overflow) ans = min(ans, mid), r = mid - 1;
                else r = mid - 1;
            }
        }
        return ans + add;
    }
};

思路2: 写太心累了,换个思路,除法也可以用位运算进行模拟。
位运算模拟除法思路:
先把被除数和除数都化为二进制。对于长度相同的被除数和除数而言,如果被除数此时值大于除数,那么说明 被除数 / 除数 此时商为1(不可能大于1,因为在二进制条件下,商大于等于2的条件是 被除数>=(除数<<1))。那我们思考当前被除数左移k位的情况,被除数除以除数的商为 1<<k。那我们只需要将被除数从高位到低位,与除数进行比较,假如被除数大于等于除数,那么向答案添加 1<<此时左移位数,并且从被除数中减去 除数<<此时左移位数。
代码2:

class Solution {
public:
    bool isPow(int x){
        if(x < 0) x = -x;
        int times = 0;
        while(x){
            if(x & 1) ++times;
            x >>= 1;
        }
        if(times > 1) return false;
        return true;
    }
    int divide(int dividend,int divisor){
    	// 前面特判和代码1一样, 就不再说了, 看代码1就行
        if(divisor == 1) return dividend;
        if(dividend == 0) return 0;
        if(dividend == divisor) return 1;
        if(divisor == -2147483648) return 0;
        if(divisor == -1){
            if(dividend == -2147483648) return 2147483647;
            else return -dividend;
        }
        // flag用于判断最终答案正负, 其余与上一代码一样
        int add = 0, flag = 1, ans = 0, tmp;
        if(dividend == -2147483648 && isPow(divisor)){
            if(divisor > 0) add = -1;
            else add = 1;
        }
        if(dividend == -2147483648) dividend++;
        if (divisor < 0) dividend = -dividend, divisor = -divisor;
        if(dividend < 0) flag = -1, dividend = -dividend;
        // 位运算模拟除法
        for(int i = 31;i >= 0;i--){
        	// 将被除数右移i位, 当他大于等于除数的时候, 说明这时候 (1<<i) 是 (被除数>>i)<<i / 除数 的商
        	// 那我们在被除数中减去这一值然后继续遍历
            tmp = (dividend >> i);
            if(tmp >= divisor){
                ans += (1 << i);
                dividend -= divisor << i;
            }
        }
        return ans * flag + add;
    }
};



Leetcode30. 串联所有单词的子串

思路: 哈希+滑动窗口的思想。因为所有字符串都是等长的,所以我们可以将字符串看为 w o r d s [ 0 ] . l e n g t h ( ) words[0].length() words[0].length()种形式,将字符串开头删除 0 − w o r d s [ 0 ] . l e n g t h ( ) 0 - words[0].length() 0words[0].length()个字符串形成每种形式。对于每种形式而言,我们从开头开始将每 w o r d s [ 0 ] . l e n g t h ( ) words[0].length() words[0].length()个字母看成一个个独立的字符串,对其进行哈希。然后开始建立滑动窗口,保证每次窗口内值合法。
保持合法性操作:

  1. 如果新加入的这个字符串不是给定字符串集合的一员,那说明包含新加入字符串的长串不可能是答案,那将左边界移到当前加入字符串的右侧,并把所有统计信息记为0;
  2. 如果新加入的这个字符串是给定字符串集合的一员,并且窗口中当前字符串数量小于字符串集合中该字符串数量,那么将这个字符串加入统计中;
  3. 如果新加入的这个字符串是给定字符串集合的一员,并且窗口中当前字符串数量大于等于字符串集合中该字符串数量,那么要从窗口左侧删除字符串,直到将当前字符串个数删减为集合中该字符串数量-1,然后执行2
    当窗口内容合法,如果当前窗口内字符串情况与集合中字符串情况相同,那么将左窗口添加到答案中;如果不相同则继续执行操作。然后将右窗口右移,执行上述保持合法性操作。
    哈希将每个字符串映射为一个数字。顺带一提,本题单哈希就够,不用担心被卡。
    代码:
class Solution {
public:
    vector<int> findSubstring(string s, vector<string>& words) {
    	// 存储答案
        vector<int> ans;
        // 记录集合中每个字符串出现的个数
        map<int,int> word;
        ans.clear(), word.clear();
        if(words.size() == 0 || words[0].length() == 0) return ans;
        // HS:哈希因子  mod:模数  has[i]:HS^i  pre[i]:从开头到字符串i-1位的哈希值
        long long HS = 13331, mod = 1000000007, has[10001] = {1}, pre[10001] = {0}, tmp, ttmp;
        // 计算每一个字符串的哈希值
        for(int i = 0;i < words.size();i++){
            tmp = 0;
            for(char j : words[i]) tmp = (tmp * HS + j) % mod;
            word[tmp] += 1;
        }
    	// 计算字符串s到的哈希值
        for(int i = 0;i < s.length();i++) pre[i + 1] = (pre[i] * HS + s[i]) % mod, has[i + 1] = (has[i] * HS) % mod;
        // 初始下标, 或者说字符串s删除了头i个字母
        for(int i = 0;i < words[0].length();i++){
            map<int,int> mp;  // 记录窗口内每个字符串出现的次数
            mp.clear();
            // l:左窗口所指字符串末尾位置+1, times:窗口内满足条件的字符串个数(直接用右窗口与左窗口计算也行,但我懒得写)
            int l = i + words[0].length(),times = 0;
            // j为右窗口位置, 所对应的是右窗口所对应字符串末尾字符位置+1
            for(int j = i + words[0].length();j <= s.length();j += words[0].length()){
            	// 计算右窗口所指字符串哈希值
                tmp = (pre[j] - pre[j - words[0].length()] * has[words[0].length()] % mod + mod) % mod;
                // 如果不在集合里, 那就清空窗口, 左窗口移到当前右窗口右侧
                if(word.find(tmp) == word.end()) mp.clear(), l = j + words[0].length(), times = 0;
                // 当前字符串出现次数少于规定次数, 加入统计
                else if(mp[tmp] < word[tmp]) mp[tmp] += 1, times++;
                // 当前字符串出现次数等于规定次数, 将窗口内当前字符串个数减一,再加入统计
                else{
                    mp[tmp] += 1;
                    while(true){
                    	// 当前左窗口所指字符串哈希值
                        ttmp = (pre[l] - pre[l - words[0].length()] * has[words[0].length()] % mod + mod) % mod;
                        // 移除字符串统计, 右移左窗口
                        mp[ttmp] -= 1;
                        l += words[0].length();
                        // 直到右窗口要添加的字符串在窗口内出现次数为集合中出现次数-1
                        if(ttmp == tmp) break;
                        times--;
                    }
                }
                // 如果当前窗口内字符串满足条件, 添加到答案中
                if(times == (int)words.size()) ans.push_back(l - words[0].length());
            }
        }
        return ans;
    }
};



Leetcode31. 下一个排列

思路: 从后往前遍历,找到第一个比后面数列最大值小的数字,将这个数字与比他大数字的最小值交换,然后把这个数字位置之后的所有数字从小到大排序。注意特别处理一下不存在下一个排列的情况(从头到尾sort)。
代码:

class Solution {
public:
    void nextPermutation(vector<int>& nums) {
        int mx = nums.size() - 1,flag = 0,tmp; // mx最大值位置, flag是否找到下一个排列, tmp临时变量
        for(int i = nums.size() - 2;i >= 0;i--){  // 找到第一个比后面最大值小的位置
            if(nums[i] < nums[mx]){  // 如果找到了
                for(int j = i + 1;j < nums.size();j++){  // 找到这个位置后面比这个位置的值大的最小值
                    if(nums[j] > nums[i] && nums[j] < nums[mx]) mx = j;
                }
                swap(nums[mx], nums[i]);  // 交换
                flag = 1;
                sort(nums.begin() + i + 1, nums.end());  // 把这个位置后面的数字升序排序
                break;
            }else mx = i;
        }
        if(!flag) sort(nums.begin(), nums.end());  // 当前是最大排列则直接从头到尾sort
        return;
    }
};



Leetcode32. 最长有效括号

思路: dp的思想。对于每一个符合条件的括号序列,最后一位必然是 ‘)’ ,所以我们考虑对于右括号而言,括号串成立的情况。

  1. 右括号与一个左括号相连,即 ‘?()’ 的情况。这时候包括最右侧括号的最长有效括号串长度为2(最后左右括号)+ n(?内最右侧符合条件的最长有效括号串长度)。例如原串为 ‘?()’,?为 ‘)(())’,那么这时候包括最右侧括号的最长有效括号串的长度为 2 + n , n = 4 2+n,n=4 2+n,n=4
  2. 右括号与多个(大于等于一个)右括号相连,即 ‘(?))’ 的情况。如果此时 ‘?)’ 为一个非法字符串,或者不存在有案例这种情况,那么当前长度为0。如果此时 ‘?)’ 为一个合法字符串,并且合法字符串左侧为一个 ‘(’,那么这时候包括最右侧括号的最长有效括号串长度为2(满足 ‘?)’ 为合法字符串的最左左括号和最右右括号)+ n(合法括号串 ‘?)’ 的长度)
    代码:
class Solution {
public:
    int longestValidParentheses(string s) {
   	// dp[i]记录以s[i-1]为结尾的合法括号串最长长度
    int dp[30004], ans = 0;
    memset(dp, 0, sizeof(dp));
    for(int i = 1;i < s.length();i++){
    	// 如果')'前一个是'(', 那么到当前为止, 最长合法括号串长度为2+'('前最长合法括号串长度
        if(s[i] == ')' && s[i - 1] == '(') dp[i + 1] = dp[i - 1] + 2;
        // 如果')'前是多个')', 并且以')'前这些')'为末尾的字符串存在一个最长合法字符串, 并且在这个合法字符串前是一个'('与末尾')'对应
        // 那么到当前为止, 最长合法括号串长度为2+末尾前最长合法字符串长度+'('前最长合法字符串长度
        else if(s[i] == ')' && s[i - 1] == ')' && i - dp[i] - 1 >= 0 && s[i - dp[i] - 1] == '(') dp[i + 1] = dp[i] + 2 + dp[i - dp[i] - 1];
        ans = max(ans, dp[i + 1]);
    }
    return ans;
}
};



Leetcode33. 搜索旋转排序数组

思路: 前半段是升序的,后半段也是升序的,并且后一段每个数字小于前一段第一个数字,那就是一个二分的思想。
代码:(其实可以写短点,为了好理解就没压行)

class Solution {
public:
    int search(vector<int>& nums, int target) {
        int l = 0, r = nums.size() - 1, mid;
        while(l <= r){
            mid = (l + r) >> 1;
            // 答案就输出
            if(nums[mid] == target) return mid;
            // 分类讨论, 如果答案是在左边的
            if(target >= nums[0]){
                if(nums[mid] >= nums[0]){
                	// 在左边的单调递增数组上二分
                    if(nums[mid] < target) l = mid + 1;
                    else r = mid - 1;
                }else r = mid - 1;  // 答案在左边, 所以当下标在右边肯定不成立
            }else{  // 如果答案在右边
                if(nums[mid] < nums[0]){
            		// 在右边的单调递增数组上二分
                    if(nums[mid] < target) l = mid + 1;
                    else r = mid - 1;
                }else l = mid + 1;  // 答案在右边, 所以当下标在左边肯定不成立
            }
        }
        return -1;
    }
};



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

思路: 二分就完事了。先二分左侧端点,再二分右侧端点。
代码:

class Solution {
public:
    vector<int> searchRange(vector<int>& nums, int target) {
        int l = 0, r = nums.size() - 1,mid,ansl = nums.size(),ansr = -1;  // ansl 为左端点位置, ansr 为右端点位置
        while(l <= r){
            mid = (l + r) >> 1;
            if(nums[mid] < target) l = mid + 1;
            else ansl = min(ansl, mid), r = mid - 1;
        }
        // 从左端点开始也许能略微快一点(
        l = ansl, r = nums.size() - 1;
        while(l <= r){
            mid = (l + r) >> 1;
            if(nums[mid] <= target) ansr = max(ansr, mid), l = mid + 1;
            else r = mid - 1;
        }
        vector<int> vt;
        vt.clear();
        if(ansr < ansl) vt.push_back(-1), vt.push_back(-1);
        else vt.push_back(ansl), vt.push_back(ansr);
        return vt;
    }
};



Leetcode35. 搜索插入位置

思路: 二分第一个大于等于他的位置。
代码:

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



Leetcode36. 有效的数独

思路: 一个有效的数独(部分已被填充)不一定是可解的。所以只要符合前三条规律即可。注意一个问题是数组定义在函数内部要初始化。
每次将当前格子的数字添加到所在行中、列中、9*9矩阵中,更新这个部分的数字个数,如果个数大于1,那么说明有重复,输出-1
代码:

class Solution {
public:
    bool isValidSudoku(vector<vector<char>>& board) {
    	// row[i][j]记录第i行数字j+1的数量
    	// col[i][j]记录第i列数字j+1的数量
    	// grid[i][j][k]记录第i行第j个9*9矩阵中数字k+1的数量
        int row[9][9], col[9][9], grid[3][3][9];
        memset(row, 0, sizeof(row));
        memset(col, 0, sizeof(col));
        memset(grid, 0, sizeof(grid));
        for(int i = 0;i < 9;i++){
            for(int j = 0;j < 9;j++){
                if(board[i][j] == '.') continue;
                if(++row[i][board[i][j] - '1'] > 1) return false;
                if(++col[j][board[i][j] - '1'] > 1) return false;
                if(++grid[i/3][j/3][board[i][j] - '1'] > 1) return false;
            }
        }
        return true;
    }
};



Leetcode37. 解数独

思路: 题目没有说如果不成立怎么输出,那就默认必然成立。直接暴力递归,对于每个空白位置而言,最多有九种情况,我们对于每种情况判断下是否合理,如果合理检测下一个空白位置的情况。如果有个位置1-9都不合法,那么回溯到上一状态,让上一状态换一个值。
代码:

class Solution {
public:
	// row[i]第i行有哪些数字, 用二进制表示, 如果row[i] & (1 << k) > 0, 则说明row[i]行中有数字k-1
	// col[i]第i列有哪些数字, 用二进制表示
	// grid[i][j]表示从左上角开始, 每个方阵有哪些数字
	// ans记录每个位置答案
	// yi[i]记录1<<i的值
	// cnt为空着的位置个数
	// emp数组记录空白位置的坐标
    int row[9], col[9], grid[3][3], ans[9][9], tmp, yi[10], cnt = 0;
    pair<int,int> emp[90];
    int dfs(int x){
    	// 如果所有空白位置都成立了, 那返回真
        if(x > cnt) return 1;
        int lx = emp[x].first, ly = emp[x].second;
        for(int i = 0;i < 9;i++){
        	// 判断行/列/方阵是否有当前数
            if(row[lx] & yi[i]) continue;
            if(col[ly] & yi[i]) continue;
            if(grid[lx / 3][ly / 3] & yi[i]) continue;
            // 更新当前位置答案,更新行/列/方阵包含的数字
            ans[lx][ly] = i + 1;
            row[lx] |= yi[i];
            col[ly] |= yi[i];
            grid[lx / 3][ly / 3] |= yi[i];
            // 检查当前情况下之后的空白位置成立情况
            if(dfs(x + 1)) return 1;
            // 不成立的时候还原行/列/方阵/答案数组
            ans[lx][ly] = 0;
            row[lx] ^= yi[i];
            col[ly] ^= yi[i];
            grid[lx / 3][ly / 3] ^= yi[i];
        }
        return 0;
    }
    void solveSudoku(vector<vector<char>>& board) {
    	// 计算1 << i的值, 方便后面计算
        for(int i = 0;i < 10;i++) yi[i] = 1 << i;
        // 统计初始情况
        for(int i = 0;i < 9;i++){
            for(int j = 0;j < 9;j++){
                if(board[i][j] == '.') emp[++cnt] = {i, j};
                else{
                    tmp = board[i][j] - '1';
                    ans[i][j] = board[i][j] - '0';
                    row[i] |= yi[tmp];
                    col[j] |= yi[tmp];
                    grid[i / 3][j / 3] |= yi[tmp];
                }

            }
        }
        dfs(1);
        // 更新答案数组
        for(int i = 0;i < 9;i++){
            for(int j = 0;j < 9;j++) board[i][j] = (char)(ans[i][j] + '0');
        }
    }
};



Leetcode38. 外观数列

思路: 暴力,从头到尾按规则寻找相同字符串将其添加到当前答案就行。
代码:

class Solution {
public:
	// x个字符y转为相应字符串
    string intToString(int x,char y){
        string s = " ";
        s[0] = y;
        while(x){
            s = " " + s;
            s[0] = (char)('0' + x % 10);
            x /= 10;
        }
        return s;
    }
    string countAndSay(int n) {
        string s = "1", ans = "1";
        for(int i = 2;i <= n;i++){
            int l = 0, r = 0;
            ans = "";
            while(l < s.length()){
                while(r < s.length() && s[l] == s[r]) ++r;
                ans = ans + intToString(r - l, s[l]);
                l = r;
            }
            s = ans;
        }
        return ans;
    }
};



Leetcode39. 组合总和

思路: 一眼dp。如果是要计算答案等于target的集合个数,那就只要简单dp即可,本题还有个需要输出集合的麻烦事。
对于每个原数列candidates中的数字而言,有两种使用方法:1.单独成集合,即k个当前数字形成的集合;2.添加到已有的集合之后。
代码:

class Solution {
public:
    vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
        vector<vector<int>> dp[45];  // dp[i]记录和为i的所有集合
        vector<int> tmp, ttmp;
        dp[0].push_back(tmp);  // dp[0]只有一个空集合
        for(int & candidate : candidates){  // 遍历原数组每个值
        	// **从大到小** 一定要从大到小, 不然前面状态的改变会影响后面
            for(int i = target - candidate; i >= 0;i--){
                if(dp[i].empty()) continue;
                tmp.clear();
                // 向当前集合后面添加j/candidate个candidate的情况
                for(int j = candidate;j + i <= target;j += candidate){
                    tmp.push_back(candidate);
                    for(auto & k : dp[i]){
                        ttmp = k;
                        ttmp.insert(ttmp.end(), tmp.begin(), tmp.end());
                        dp[j + i].push_back(ttmp);
                    }
                }
            }
        }
        return dp[target];
    }
};



Leetcode40. 组合总和 II

思路: 与上一题一样,区别在于不能每个数字使用无限次。但是原数组中可能有相同的数字,那么就相当于对于数组中每个不相同的数字,每个数字可以使用有限个,那么加上个数限制即可。
代码:

class Solution {
public:
    vector<vector<int>> combinationSum2(vector<int>& candidates, int target) {
        std::sort(candidates.begin(), candidates.end());  // 对于数组排序
        vector<int> tmp, ttmp;
        vector<vector<int>> dp[45];
        dp[0].push_back(tmp);
        int l = 0, r = 0;
        while(l < candidates.size()){
        	// 找到当前数字个数
            while(r < candidates.size() && candidates[r] == candidates[l]) ++r;
            for(int i = target - candidates[l];i >= 0;i--){
                if(dp[i].empty()) continue;
                tmp.clear();
                // 多加个数字个数限制
                for(int j = candidates[l];j + i <= target && j / candidates[l] <= (r - l);j += candidates[l]){
                    tmp.push_back(candidates[l]);
                    for(auto & k : dp[i]){
                        ttmp = k;
                        ttmp.insert(ttmp.end(), tmp.begin(), tmp.end());
                        dp[j + i].push_back(ttmp);
                    }
                }
            }
            l = r;
        }
        return dp[target];
    }
};
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值