【力扣刷题笔记】中级算法

中级算法(7.22-8.21)

数组和字符串

三数之和

给你一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?请你找出所有和为 0 且不重复的三元组。

注意:答案中不可以包含重复的三元组。

示例 1:

输入:nums = [-1,0,1,2,-1,-4]
输出:[[-1,-1,2],[-1,0,1]]

示例 2:

输入:nums = []
输出:[]

示例 3:

输入:nums = [0]
输出:[]

提示:

0 <= nums.length <= 3000
-105 <= nums[i] <= 105

链接:https://leetcode.cn/leetbook/read/top-interview-questions-medium/xvpj16/

我的解法

企图用哈希解法未遂

去重的过程不好处理,有很多小细节

【力扣刷题笔记】中级算法_第1张图片

标答

思路:排序+双指针

class Solution {
public:
    vector> threeSum(vector& nums) 
    {
        int size = nums.size();
        if (size < 3)   return {};          // 特判
        vector >res;            // 保存结果(所有不重复的三元组)
        std::sort(nums.begin(), nums.end());// 排序(默认递增)
        for (int i = 0; i < size; i++)      // 固定第一个数,转化为求两数之和
        {
            if (nums[i] > 0)    return res; // 第一个数大于 0,后面都是递增正数,不可能相加为零了
            // 去重:如果此数已经选取过,跳过
            if (i > 0 && nums[i] == nums[i-1])  continue;
            // 双指针在nums[i]后面的区间中寻找和为0-nums[i]的另外两个数
            int left = i + 1;
            int right = size - 1;
            while (left < right)
            {
                if (nums[left] + nums[right] > -nums[i])
                    right--;    // 两数之和太大,右指针左移
                else if (nums[left] + nums[right] < -nums[i])
                    left++;     // 两数之和太小,左指针右移
                else
                {
                    // 找到一个和为零的三元组,添加到结果中,左右指针内缩,继续寻找
                    res.push_back(vector{nums[i], nums[left], nums[right]});
                    left++;
                    right--;
                    // 去重:第二个数和第三个数也不重复选取
                    // 例如:[-4,1,1,1,2,3,3,3], i=0, left=1, right=5
                    while (left < right && nums[left] == nums[left-1])  left++;
                    while (left < right && nums[right] == nums[right+1])    right--;
                }
            }
        }
        return res;
    }
};

根据标答自己写的代码

class Solution {
public:
    vector> threeSum(vector& nums) {
        vector> ans;
        sort(nums.begin(),nums.end());
        int n=nums.size();
        for(int i=0;i=1&&nums[i]==nums[i-1])
            {
                continue;
            }
            int target=-nums[i];
            int l=i+1,r=n-1;
            while(ltarget)
                {
                    while(r>0&&nums[r]==nums[r-1])
                        r--;
                    r--;
                }
                else
                {
                    vector row;
                    row.push_back(nums[i]);
                    row.push_back(nums[l]);
                    row.push_back(nums[r]);
                    ans.push_back(row);

                    while(l0&&nums[r]==nums[r-1])
                        r--;
                    r--;
                }
            }
        }
        return ans;
    }
};
疑惑解答

1.为什么双指针的单向运行不会使正确答案跳过?

思路:证明每一个正确答案都会被发现

2.为什么第一层循环可以跳过重复的元素而不会导致漏解

由于i

矩阵置零

给定一个 m x n 的矩阵,如果一个元素为 0 ,则将其所在行和列的所有元素都设为 0 。请使用 原地 算法。

示例 1:

【力扣刷题笔记】中级算法_第2张图片

输入:matrix = [[1,1,1],[1,0,1],[1,1,1]]
输出:[[1,0,1],[0,0,0],[1,0,1]]

示例 2:

【力扣刷题笔记】中级算法_第3张图片

输入:matrix = [[0,1,2,0],[3,4,5,2],[1,3,1,5]]
输出:[[0,0,0,0],[0,4,5,0],[0,3,1,0]]

提示:

m == matrix.length
n == matrix[0].length
1 <= m, n <= 200
-2^31 <= matrix[i][j] <= 2^31 - 1

进阶:

一个直观的解决方案是使用 O(mn) 的额外空间,但这并不是一个好的解决方案。
一个简单的改进方案是使用 O(m + n) 的额外空间,但这仍然不是最好的解决方案。
你能想出一个仅使用常量空间的解决方案吗?
链接:https://leetcode.cn/leetbook/read/top-interview-questions-medium/xvmy42/

我的解法

没有想到比O(m+n)更好的算法了

【力扣刷题笔记】中级算法_第4张图片

class Solution {
public:
    void setZeroes(vector>& matrix) {
        int m=matrix.size(),n=matrix[0].size();
        vector row(n,0);
        vector col(m,0);
        
        for(int i=0;i
官解

一、使用两个标记变量

我们可以用矩阵的第一行和第一列代替方法一中的两个标记数组,以达到 O(1)的额外空间。但这样会导致原数组的第一行和第一列被修改,无法记录它们是否原本包含 0。因此我们需要额外使用两个标记变量分别记录第一行和第一列是否原本包含 0。

class Solution {
public:
    void setZeroes(vector>& matrix) {
        int m = matrix.size();
        int n = matrix[0].size();
        int flag_col0 = false, flag_row0 = false;
        for (int i = 0; i < m; i++) {
            if (!matrix[i][0]) {
                flag_col0 = true;
            }
        }
        for (int j = 0; j < n; j++) {
            if (!matrix[0][j]) {
                flag_row0 = true;
            }
        }
        for (int i = 1; i < m; i++) {
            for (int j = 1; j < n; j++) {
                if (!matrix[i][j]) {
                    matrix[i][0] = matrix[0][j] = 0;
                }
            }
        }
        for (int i = 1; i < m; i++) {
            for (int j = 1; j < n; j++) {
                if (!matrix[i][0] || !matrix[0][j]) {
                    matrix[i][j] = 0;
                }
            }
        }
        if (flag_col0) {
            for (int i = 0; i < m; i++) {
                matrix[i][0] = 0;
            }
        }
        if (flag_row0) {
            for (int j = 0; j < n; j++) {
                matrix[0][j] = 0;
            }
        }
    }
};

二、使用一个变量标记

我们可以对方法二进一步优化,只使用一个标记变量记录第一列是否原本存在 0。这样,第一列的第一个元素即可以标记第一行是否出现 0。

但为了防止每一列的第一个元素被提前更新,我们需要从最后一行开始,倒序地处理矩阵元素。

官解虽短但不好理解,自己写了一个

class Solution {
public:
    void setZeroes(vector>& matrix) {
        bool col0=true;
        int m=matrix.size(),n=matrix[0].size();
        for(int i=0;i
总结思考

1、当利用常数空间时,想想是不是可以把结构塞到原有结构中

2、正确性证明:

关键:覆盖了的本来也需要改,没覆盖的正好不需要改

[1…n] [1…n]:算法正确性同利用O(m+n)空间算法,唯一要说明的是若本来第一行、第一列为0,则[1…n] [1…n]也要修改(有点外力指定那种意思)

第一行(列):若被上一过程[1…n] [1…n]修改成0了,说明它本来也要被改成0,修改正确!若没有被修改则说明当前列(行)没有0,唯一被修改的可能就是因为第一行内部本来就有的0;若row0标记true,则修改为0,正确!若row0标记false,则不修改,正确!

字母异位词分组

给你一个字符串数组,请你将 字母异位词 组合在一起。可以按任意顺序返回结果列表。

字母异位词 是由重新排列源单词的字母得到的一个新单词,所有源单词中的字母通常恰好只用一次。

示例 1:

输入: strs = ["eat", "tea", "tan", "ate", "nat", "bat"]
输出: [["bat"],["nat","tan"],["ate","eat","tea"]]

示例 2:

输入: strs = [""]
输出: [[""]]

示例 3:

输入: strs = ["a"]
输出: [["a"]]

提示:

  • 1 <= strs.length <= 104
  • 0 <= strs[i].length <= 100
  • strs[i] 仅包含小写字母
我的解法
class Solution {
public:
    vector> groupAnagrams(vector& strs) {
        vector> ans;
        vector reorder(strs);
        int n=strs.size();
        for(int i=0;i temp;
                temp.push_back(strs[i]);
                ans.push_back(temp);
            }
        }
        return ans;
    }

    void quicksort(vector& strs,vector& reorder,int l,int r)
    {
        if(l>=r)
        {
            return;
        }
        int pos=partition(strs,reorder,l,r);
        quicksort(strs,reorder,l,pos-1);
        quicksort(strs,reorder,pos+1,r);
    }
    int partition(vector& strs,vector& reorder,int l,int r)
    {
        string pivot=reorder[l];
        int j=l;
        for(int i=l+1;i<=r;i++)
        {
            if(reorder[i]
官解

两个字符串互为字母异位词,当且仅当两个字符串包含的字母相同。同一组字母异位词中的字符串具备相同点,可以使用相同点作为一组字母异位词的标志,使用哈希表存储每一组字母异位词,哈希表的键为一组字母异位词的标志,哈希表的值为一组字母异位词列表。

遍历每个字符串,对于每个字符串,得到该字符串所在的一组字母异位词的标志,将当前字符串加入该组字母异位词的列表中。遍历全部字符串之后,哈希表中的每个键值对即为一组字母异位词。

以下的两种方法分别使用排序和计数作为哈希表的键。

class Solution {
public:
    vector> groupAnagrams(vector& strs) {
        unordered_map> mp;
        for (string& str: strs) {
            string key = str;
            sort(key.begin(), key.end());
            mp[key].emplace_back(str);
        }
        vector> ans;
        for (auto it = mp.begin(); it != mp.end(); ++it) {
            ans.emplace_back(it->second);
        }
        return ans;
    }
};
	class Solution {
	public:
		vector> groupAnagrams(vector& strs) {
			auto arrayHash = [fn = hash{}](const array& arr)->size_t {
                /*
                * accumulate()中的第四个参数项要传入一个二元操作(BinaryOperation)规则,告诉它如何将当前元素与累积量做操作
                * 它隐式地调用(size_t)acc和(int)num这两个量,默认情况下做简单的相加运算。
                */
				return accumulate(arr.begin(), arr.end(), (size_t)0, [&](size_t acc, int num) {
                    return (acc << 1) ^ fn(num); // hash运算结果移位相加
                    });
			};
            /*
            * 由于key的类型是array,属于用户自定义的一个数据类型,编译器无法针对用户自定义的数据类型做两个元素是否相等的判断
            * 所以,要告诉unordered_map你是如何确定key与key之间是否冲突(或者是否相等)的。
            * 正因如此,unordered_map的API中的第三个参数项就会让用户传入一个运算规则的函数的类型
            * 而前面写的arrayHash这个类(class)实际上是一个lambda expression(你也可以把它看作是一个仿函数(functor)的类)
            * 要获取一个显式的类的类型是容易的,但这里是隐式的,就要借助于decltype获得arrayHash的type了
            * (补充一点,lambda表达式很多都是通过decltype()的技巧获取其类型的,这也是C++11的重大进步之一)
            */
			unordered_map, vector, decltype(arrayHash)> mp(0, arrayHash);
			for (string& str : strs) {
				array counts{};
				int length = (int)str.length();
				for (int i = 0; i < length; ++i) counts[(size_t)str[i] - 'a'] ++;
                /*
                * mp[counts]过程就会调用arrayHash计算counts的hash值
                * 在哈希表中寻找对应哈希值的篮子(busket),并将该counts对应的str挂在对应篮子的链表尾部
                * (对于不存在的哈希值,对应的篮子是一个空链表,将str挂篮子的链表尾部这一操作仍然不变,
                * 如此一来就统一了emplace_back()的操作:只负责在篮子的链表的尾部添加string)
                */
				mp[counts].emplace_back(str);
			}
			vector> ans;
			for (auto it = mp.begin(); it != mp.end(); ++it) ans.emplace_back(it->second);
			return ans;
		}
	};
反思

遇到这种有共同特征的一组数/字符串想想哈希!

无重复字符的最长子串

给定一个字符串 s ,请你找出其中不含有重复字符的 最长子串 的长度。

示例 1:

输入: s = "abcabcbb"
输出: 3 
解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。

示例 2:

输入: s = "bbbbb"
输出: 1
解释: 因为无重复字符的最长子串是 "b",所以其长度为 1。

示例 3:

输入: s = "pwwkew"
输出: 3
解释: 因为无重复字符的最长子串是 "wke",所以其长度为 3。
     请注意,你的答案必须是 子串 的长度,"pwke" 是一个子序列,不是子串。

提示:

0 <= s.length <= 5 * 10^4
s 由英文字母、数字、符号和空格组成

链接:https://leetcode.cn/leetbook/read/top-interview-questions-medium/xv2kgi/

我的解法

双指针(滑动窗口)

class Solution {
public:
    int lengthOfLongestSubstring(string s) {
        unordered_map map;
        int n=s.size();
        int i=0,j=0;
        int ans=0;
        cout<ans)
                ans=j-i;
            while(s[j]!=s[i])
            {
                map.erase(s[i++]);
            }
            map.erase(s[i++]);
        }
        return ans;
    }
};

DP

class Solution {
public:
    int lengthOfLongestSubstring(string s) {
    int n=s.length();
    if(n==0)
    {
        return 0;
    }
    int* last=new int[n];
    unordered_map map;
    for(int i=0;i=i-dp[i-1])
        {
            dp[i]=i-last[i];
        }
        else
        {
            dp[i]=dp[i-1]+1;
        }
    }
    int ans=0;
    for(int i=0;i

最长回文子串

给你一个字符串 s,找到 s 中最长的回文子串。

示例 1:

输入:s = "babad"
输出:"bab"
解释:"aba" 同样是符合题意的答案。

示例 2:

输入:s = "cbbd"
输出:"bb"

提示:

1 <= s.length <= 1000
s 仅由数字和英文字母组成
我的解法

DP(一开始用的一维DP,差点没写出来)

class Solution {
public:
    string longestPalindrome(string s) {
        int n=s.size();
        string ans;int l=0,r=0,max=1;
        vector> dp(n);
        for(int i=0;i=0;i--)
        {
            for(int j=i+1;jmax)
                {
                    max=j-i+1;
                    l=i;r=j;
                }
            }
        }
        ans=s.substr(l,max);
        return ans;
    }
};
Manacher 算法

https://www.jianshu.com/p/392172762e55

class Solution {
public:
    string longestPalindrome(string s) {
        int n=s.size();
        string t="";
        for(int i=0;i len(2*n+1,1);
        int mid=0;//最远中心
        int right=0;//最远右端
        for(int i=0;i<2*n+1;i++)
        {
            if(right>i)
            {
                len[i]=min(len[2*mid-i],right-i);
            }
            while(i+len[i]<2*n+1&&i-len[i]>=0&&t[i+len[i]]==t[i-len[i]])
            {
                len[i]++;
            }
            if(i+len[i]-1>right)
            {
                right=i+len[i]-1;
                mid=i;
            }
        }

        int maxlen=0,pos=0;;
        for(int i=0;i<2*n+1;i++)
        {
            if(len[i]>maxlen)
            {
                maxlen=len[i];
                pos=i;
            }
        }
        return s.substr((pos-len[pos]+1)/2,len[pos]-1);
    }
};

时间复杂度:for 循环里边套了一层 while 循环,难道不是 O ( n² )?不!其实是 O ( n )。不严谨的想一下,因为 while 循环访问 R 右边的数字用来扩展,也就是那些还未求出的节点,然后不断扩展,而期间访问的节点下次就不会再进入 while 了,可以利用对称得到自己的解,所以每个节点访问都是常数次,所以是O ( n )。

递增的三元子序列

给你一个整数数组 nums ,判断这个数组中是否存在长度为 3 的递增子序列。

如果存在这样的三元组下标 (i, j, k) 且满足 i < j < k ,使得 nums[i] < nums[j] < nums[k] ,返回 true ;否则,返回 false 。

示例 1:

输入:nums = [1,2,3,4,5]
输出:true
解释:任何 i < j < k 的三元组都满足题意

示例 2:

输入:nums = [5,4,3,2,1]
输出:false
解释:不存在满足题意的三元组

示例 3:

输入:nums = [2,1,5,0,4,6]
输出:true
解释:三元组 (3, 4, 5) 满足题意,因为 nums[3] == 0 < nums[4] == 4 < nums[5] == 6

提示:

1 <= nums.length <= 5 * 10^5
-2^31 <= nums[i] <= 2^31 - 1

进阶:你能实现时间复杂度为 O(n) ,空间复杂度为 O(1) 的解决方案吗?

链接:https://leetcode.cn/leetbook/read/top-interview-questions-medium/xvvuqg/

解法

一、双向遍历

class Solution {
public:
    bool increasingTriplet(vector& nums) {
        int n = nums.size();
        if (n < 3) {
            return false;
        }
        vector leftMin(n);
        leftMin[0] = nums[0];
        for (int i = 1; i < n; i++) {
            leftMin[i] = min(leftMin[i - 1], nums[i]);
        }
        vector rightMax(n);
        rightMax[n - 1] = nums[n - 1];
        for (int i = n - 2; i >= 0; i--) {
            rightMax[i] = max(rightMax[i + 1], nums[i]);
        }
        for (int i = 1; i < n - 1; i++) {
            if (nums[i] > leftMin[i - 1] && nums[i] < rightMax[i + 1]) {
                return true;
            }
        }
        return false;
    }
};

二、一次遍历贪心

class Solution {
public:
    bool increasingTriplet(vector& nums) {
        int n=nums.size();
        vector d;
        d.push_back(nums[0]);
        for(int i=1;i

*300. 最长递增子序列

给你一个整数数组 nums ,找到其中最长严格递增子序列的长度。

子序列 是由数组派生而来的序列,删除(或不删除)数组中的元素而不改变其余元素的顺序。例如,[3,6,2,7] 是数组 [0,3,1,6,2,2,7] 的子序列。

示例 1:

输入:nums = [10,9,2,5,3,7,101,18]
输出:4
解释:最长递增子序列是 [2,3,7,101],因此长度为 4 。

示例 2:

输入:nums = [0,1,0,3,2,3]
输出:4

示例 3:

输入:nums = [7,7,7,7,7,7,7]
输出:1

提示:

  • 1 <= nums.length <= 2500
  • -104 <= nums[i] <= 104
题解

参照:https://leetcode.cn/problems/longest-increasing-subsequence/solution/yi-bu-yi-bu-tui-dao-chu-guan-fang-zui-you-jie-fa-x/

关键就在于这张图:表示所有可能在将来成为最长递增子序列的序列

与DP的感觉差不多,表示【0-i】里长度为k上升序列尾端的最小值

【力扣刷题笔记】中级算法_第5张图片

讲的很好!

class Solution {
public:
    int lengthOfLIS(vector& nums) {
        int n=nums.size();
        vector d;
        d.push_back(nums[0]);
        for(int i=1;i

lower/upper_bound()的底层也是二分查找

【力扣刷题笔记】中级算法_第6张图片

链表

*相交链表

给你两个单链表的头节点 headA 和 headB ,请你找出并返回两个单链表相交的起始节点。如果两个链表不存在相交节点,返回 null 。

题目数据 保证 整个链式结构中不存在环。

注意,函数返回结果后,链表必须 保持其原始结构 。

链接:https://leetcode.cn/leetbook/read/top-interview-questions-medium/xv02ut/

进阶:你能否设计一个时间复杂度 O(m + n) 、仅用 O(1) 内存的解决方案?

我的解法

空间O(m+n)

class Solution {
public:
    ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
        unordered_map map;
        while(headA!=NULL)
        {
            map[headA]+=1;
            headA=headA->next;
        }
        while(headB!=NULL)
        {
            if(map.count(headB))
            {
                return headB;
            }
            headB=headB->next;
        }
        return NULL;
    }
};
官解

如果两个链表相交,那么相交点之后的长度是相同的

我们需要做的事情是,让两个链表从同距离末尾同等距离的位置开始遍历。这个位置只能是较短链表的头结点位置。

指针 pA 指向 A 链表,指针 pB 指向 B 链表,依次往后遍历
如果 pA 到了末尾,则 pA = headB 继续遍历
如果 pB 到了末尾,则 pB = headA 继续遍历

回溯算法

17. 电话号码的字母组合

给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。答案可以按 任意顺序 返回。

给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。

【力扣刷题笔记】中级算法_第7张图片

示例 1:

输入:digits = "23"
输出:["ad","ae","af","bd","be","bf","cd","ce","cf"]

示例 2:

输入:digits = ""
输出:[]

示例 3:

输入:digits = "2"
输出:["a","b","c"]

提示:

  • 0 <= digits.length <= 4
  • digits[i] 是范围 ['2', '9'] 的一个数字。
我的解法

回溯:实际遍历过程相当于DFS!

class Solution {
public:
    vector ans;
    string s;
    int n;   
    unordered_map map
    {
        {'2',"abc"},
        {'3',"def"},
        {'4',"ghi"},
        {'5',"jkl"},
        {'6',"mno"},
        {'7',"pqrs"},
        {'8',"tuv"},
        {'9',"wxyz"}
    };

    vector letterCombinations(string digits) {
        n=digits.size();
        if(n==0)    return ans;
        backtrace(0,digits);
        return ans;
    }

    void backtrace(int i,string& digits)
    {
        if(i==n)
        {
            ans.push_back(s);
        }
        for(char c:map[digits[i]])
        {
            s+=c;
            backtrace(i+1,digits);
            s.pop_back();
        }
    }
};
另外一种解法

队列:实际遍历过程相当于BFS!

class Solution {
	public List<String> letterCombinations(String digits) {
		if(digits==null || digits.length()==0) {
			return new ArrayList<String>();
		}
		//一个映射表,第二个位置是"abc“,第三个位置是"def"。。。
		//这里也可以用map,用数组可以更节省点内存
		String[] letter_map = {
			" ","*","abc","def","ghi","jkl","mno","pqrs","tuv","wxyz"
		};
		List<String> res = new ArrayList<>();
		//先往队列中加入一个空字符
		res.add("");
		for(int i=0;i<digits.length();i++) {
			//由当前遍历到的字符,取字典表中查找对应的字符串
			String letters = letter_map[digits.charAt(i)-'0'];
			int size = res.size();
			//计算出队列长度后,将队列中的每个元素挨个拿出来
			for(int j=0;j<size;j++) {
				//每次都从队列中拿出第一个元素
				String tmp = res.remove(0);
				//然后跟"def"这样的字符串拼接,并再次放到队列中
				for(int k=0;k<letters.length();k++) {
					res.add(tmp+letters.charAt(k));
				}
			}
		}
		return res;
	}
}
总结

回溯模版

void backtrack(参数) {
    if (终止条件) {
        存放结果;
        return;
    }

    for (选择:本层集合中元素(树中节点孩子的数量就是集合的大小)) {
        处理节点;
        backtrack(路径,选择列表); // 递归
        回溯,撤销处理结果
    }
}

22. 括号生成

难度中等2789

数字 n 代表生成括号的对数,请你设计一个函数,用于能够生成所有可能的并且 有效的 括号组合。

示例 1:

输入:n = 3
输出:["((()))","(()())","(())()","()(())","()()()"]

示例 2:

输入:n = 1
输出:["()"]

提示:

  • 1 <= n <= 8
我的解法
class Solution {
public:
    vector ans;
    string s;

    vector generateParenthesis(int n) {
        trackback(0,0,n);
        return ans;
    }

    void trackback(int count,int diff,int n)//(count+diff)/2是(数,(count-diff)/2是)数
    {
        if(count==2*n)
        {
            ans.push_back(s);
        }
        else
        {
            if((count+diff)/20)
                {
                    string t="()";
                    for(char c:t)
                    {
                        s+=c;
                        trackback(count+1,diff+(c=='('?1:-1),n);
                        s.pop_back();
                    }
                }
                else
                {
                    s+='(';
                    trackback(count+1,diff+1,n);
                    s.pop_back();
                }
            }
            else
            {
                s+=')';
                trackback(count+1,diff-1,n);
                s.pop_back();
            }
        }
    } 
};
官解

差不多但是代码更加优美

class Solution {
    void backtrack(vector& ans, string& cur, int open, int close, int n) {
        if (cur.size() == n * 2) {
            ans.push_back(cur);
            return;
        }
        if (open < n) {
            cur.push_back('(');
            backtrack(ans, cur, open + 1, close, n);
            cur.pop_back();
        }
        if (close < open) {
            cur.push_back(')');
            backtrack(ans, cur, open, close + 1, n);
            cur.pop_back();
        }
    }
public:
    vector generateParenthesis(int n) {
        vector result;
        string current;
        backtrack(result, current, 0, 0, n);
        return result;
    }
};

79. 单词搜索

难度中等1391

给定一个 m x n 二维字符网格 board 和一个字符串单词 word 。如果 word 存在于网格中,返回 true ;否则,返回 false

单词必须按照字母顺序,通过相邻的单元格内的字母构成,其中“相邻”单元格是那些水平相邻或垂直相邻的单元格。同一个单元格内的字母不允许被重复使用。

示例 1:

【力扣刷题笔记】中级算法_第8张图片

输入:board = [["A","B","C","E"],["S","F","C","S"],["A","D","E","E"]], word = "ABCCED"
输出:true

示例 2:

【力扣刷题笔记】中级算法_第9张图片

输入:board = [["A","B","C","E"],["S","F","C","S"],["A","D","E","E"]], word = "SEE"
输出:true

示例 3:

【力扣刷题笔记】中级算法_第10张图片

输入:board = [["A","B","C","E"],["S","F","C","S"],["A","D","E","E"]], word = "ABCB"
输出:false

提示:

  • m == board.length
  • n = board[i].length
  • 1 <= m, n <= 6
  • 1 <= word.length <= 15
  • boardword 仅由大小写英文字母组成

**进阶:**你可以使用搜索剪枝的技术来优化解决方案,使其在 board 更大的情况下可以更快解决问题?

我的解法
class Solution {
public:
    string cur;
    int m,n,len;
    bool ans;

    bool exist(vector>& board, string word) {
        len=word.size();
        m=board.size();
        n=board[0].size();
        ans=false;
        trackback(0,0,0,board,word);
        return ans;
    }

    void trackback(int l,int row,int col,vector>& board, string word)
    {
        if(l==len)
        {
            ans=true;
        }
        else if(l==0)
        {
            for(int i=0;i0&&board[row-1][col]==word[l])
            {
                char temp=board[row-1][col];
                board[row-1][col]='#';//标记为已用
                trackback(l+1,row-1,col,board,word);
                board[row-1][col]=temp;
            }
            if(row0&&board[row][col-1]==word[l])
            {
                char temp=board[row][col-1];
                board[row][col-1]='#';//标记为已用
                trackback(l+1,row,col-1,board,word);
                board[row][col-1]=temp;
            }
            if(col
官解

思路一致但代码更为优美

class Solution {
public:
    bool check(vector>& board, vector>& visited, int i, int j, string& s, int k) {
        if (board[i][j] != s[k]) {
            return false;
        } else if (k == s.length() - 1) {
            return true;
        }
        visited[i][j] = true;
        vector> directions{{0, 1}, {0, -1}, {1, 0}, {-1, 0}};
        bool result = false;
        for (const auto& dir: directions) {
            int newi = i + dir.first, newj = j + dir.second;
            if (newi >= 0 && newi < board.size() && newj >= 0 && newj < board[0].size()) {
                if (!visited[newi][newj]) {
                    bool flag = check(board, visited, newi, newj, s, k + 1);
                    if (flag) {
                        result = true;
                        break;
                    }
                }
            }
        }
        visited[i][j] = false;
        return result;
    }

    bool exist(vector>& board, string word) {
        int h = board.size(), w = board[0].size();
        vector> visited(h, vector(w));//学习二维数组初始化方法
        for (int i = 0; i < h; i++) {
            for (int j = 0; j < w; j++) {
                bool flag = check(board, visited, i, j, word, 0);
                if (flag) {
                    return true;
                }
            }
        }
        return false;
    }
};

排序与搜索

347. 前 K 个高频元素

难度中等1274

给你一个整数数组 nums 和一个整数 k ,请你返回其中出现频率前 k 高的元素。你可以按 任意顺序 返回答案。

示例 1:

输入: nums = [1,1,1,2,2,3], k = 2
输出: [1,2]

示例 2:

输入: nums = [1], k = 1
输出: [1]

提示:

  • 1 <= nums.length <= 105
  • k 的取值范围是 [1, 数组中不相同的元素的个数]
  • 题目数据保证答案唯一,换句话说,数组中前 k 个高频元素的集合是唯一的

**进阶:**你所设计算法的时间复杂度 必须 优于 O(n log n) ,其中 n 是数组大小。

我的解法

不好,没必要全部都排序出来

class Solution {
public:
    vector topKFrequent(vector& nums, int k) {
        unordered_map hash;
        multimap> map;
        vector ans;
        for(int i=0;isecond);
            map.erase(it);
        }

        return ans;
    }
};
官解

一、堆

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

    vector topKFrequent(vector& nums, int k) {
        unordered_map occurrences;
        for (auto& v : nums) {
            occurrences[v]++;
        }

        // pair 的第一个元素代表数组的值,第二个元素代表了该值出现的次数
        priority_queue, vector>, decltype(&cmp)> q(cmp);
        for (auto& [num, count] : occurrences) {
            if (q.size() == k) {
                if (q.top().second < count) {
                    q.pop();
                    q.emplace(num, count);
                }
            } else {
                q.emplace(num, count);
            }
        }
        vector ret;
        while (!q.empty()) {
            ret.emplace_back(q.top().first);
            q.pop();
        }
        return ret;
    }
};

二、快排

class Solution {
public:
    void qsort(vector>& v, int start, int end, int k) {
        int picked = rand() % (end - start + 1) + start;
        swap(v[picked], v[start]);

        int pivot = v[start].second;
        int index = start;
        for (int i = start + 1; i <= end; i++) {
            if (v[i].second >= pivot) {
                swap(v[++index], v[i]);
            }
        }
        swap(v[start], v[index]);

        if (k < index - start)
            qsort(v, start, index - 1, k);
        else if (k > index - start + 1)
            qsort(v, index + 1, end, k - (index - start + 1));
        else
            return;
    }

    vector topKFrequent(vector& nums, int k) {
        unordered_map occurrences;
        for (auto& v: nums) {
            occurrences[v]++;
        }

        vector> values;
        for (auto& kv: occurrences) {
            values.push_back(kv);
        }
        
        qsort(values, 0, values.size() - 1, k);
        vector ret;
        for(int i=0;i

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

给定整数数组 nums 和整数 k,请返回数组中第 **k** 个最大的元素。

请注意,你需要找的是数组排序后的第 k 个最大的元素,而不是第 k 个不同的元素。

示例 1:

输入: [3,2,1,5,6,4], k = 2
输出: 5

示例 2:

输入: [3,2,3,1,2,4,5,5,6], k = 4
输出: 4

提示:

  • 1 <= k <= nums.length <= 105
  • -104 <= nums[i] <= 104
解法

一、partition

class Solution {
public:
    int findKthLargest(vector& nums, int k) {
        return qsort(nums,0,nums.size()-1,k);
    }

    int qsort(vector& v, int start, int end, int k) {
        //parition
        int picked = rand() % (end - start + 1) + start;
        swap(v[picked], v[start]);

        int pivot = v[start];
        int index = start;
        for (int i = start + 1; i <= end; i++) {
            if (v[i] >= pivot) {
                swap(v[++index], v[i]);
            }
        }
        swap(v[start], v[index]);

        if (k <= index - start)
            return qsort(v, start, index - 1, k);
        else if (k > index - start + 1)
            return qsort(v, index + 1, end, k - (index - start + 1));
        else
            return v[index];
    }
};

二、堆

建堆、删除

class Solution {
public:
    void maxHeapify(vector& a, int i, int heapSize) {
        int l = i * 2 + 1, r = i * 2 + 2, largest = i;
        if (l < heapSize && a[l] > a[largest]) {
            largest = l;
        } 
        if (r < heapSize && a[r] > a[largest]) {
            largest = r;
        }
        if (largest != i) {
            swap(a[i], a[largest]);
            maxHeapify(a, largest, heapSize);
        }
    }

    void buildMaxHeap(vector& a, int heapSize) {
        for (int i = heapSize / 2; i >= 0; --i) {
            maxHeapify(a, i, heapSize);
        } 
    }

    int findKthLargest(vector& nums, int k) {
        int heapSize = nums.size();
        buildMaxHeap(nums, heapSize);
        for (int i = nums.size() - 1; i >= nums.size() - k + 1; --i) {
            swap(nums[0], nums[i]);
            --heapSize;
            maxHeapify(nums, 0, heapSize);
        }
        return nums[0];
    }
};

*56. 合并区间

以数组 intervals 表示若干个区间的集合,其中单个区间为 intervals[i] = [starti, endi] 。请你合并所有重叠的区间,并返回 一个不重叠的区间数组,该数组需恰好覆盖输入中的所有区间

示例 1:

输入:intervals = [[1,3],[2,6],[8,10],[15,18]]
输出:[[1,6],[8,10],[15,18]]
解释:区间 [1,3] 和 [2,6] 重叠, 将它们合并为 [1,6].

示例 2:

输入:intervals = [[1,4],[4,5]]
输出:[[1,5]]
解释:区间 [1,4] 和 [4,5] 可被视为重叠区间。

提示:

  • 1 <= intervals.length <= 104
  • intervals[i].length == 2
  • 0 <= starti <= endi <= 104
解法

维护前面区间中最右边的端点为ed。从前往后枚举每一个区间,判断是否应该将当前区间视为新区间。

引理:如果我们按照区间的左端点排序,那么在排完序的列表中,可以合并的区间一定是连续的。

【力扣刷题笔记】中级算法_第11张图片

证明:两个可以合并的区间中间没有不可以和他们俩合并的区间(有点并查集的感觉、染色)

上述算法的正确性可以用反证法来证明:在排完序后的数组中,两个本应合并的区间没能被合并,那么说明存在这样的三元组 (i, j, k)以及数组中的三个区间 a[i], a[j], a[k]满足 i < j < k 并且 (a[i], a[k])可以合并,但 (a[i],a[j]) 和 (a[j],a[k]) 不能合并。这说明它们满足下面的不等式:

$a[i].end < a[j].start \quad (a[i] \text{ 和 } a[j] \text{ 不能合并}) \ a[j].end < a[k].start \quad (a[j] \text{ 和 } a[k] \text{ 不能合并}) \ a[i].end \geq a[k].start \quad (a[i] \text{ 和 } a[k] \text{ 可以合并}) \$

我们联立这些不等式(注意还有一个显然的不等式 a [ j ] . s t a r t ≤ a [ j ] . e n d a[j].start \leq a[j].end a[j].starta[j].end),可以得到:

[ i ] . e n d < a [ j ] . s t a r t ≤ a [ j ] . e n d < a [ k ] . s t a r t [i].end < a[j].start \leq a[j].end < a[k].start [i].end<a[j].starta[j].end<a[k].start

产生了矛盾!这说明假设是不成立的。因此,所有能够合并的区间都必然是连续的。

class Solution {
public:
    vector> merge(vector>& intervals) {
        if (intervals.size() == 0) {
            return {};
        }
        sort(intervals.begin(), intervals.end());
        vector> merged;
        for (int i = 0; i < intervals.size(); ++i) {
            int L = intervals[i][0], R = intervals[i][1];
            if (!merged.size() || merged.back()[1] < L) {
                merged.push_back({L, R});
            }
            else {
                merged.back()[1] = max(merged.back()[1], R);
            }
        }
        return merged;
    }
};

动态规划

55. 跳跃游戏

难度中等1942

给定一个非负整数数组 nums ,你最初位于数组的 第一个下标

数组中的每个元素代表你在该位置可以跳跃的最大长度。

判断你是否能够到达最后一个下标。

示例 1:

输入:nums = [2,3,1,1,4]
输出:true
解释:可以先跳 1 步,从下标 0 到达下标 1, 然后再从下标 1 跳 3 步到达最后一个下标。

示例 2:

输入:nums = [3,2,1,0,4]
输出:false
解释:无论怎样,总会到达下标为 3 的位置。但该下标的最大跳跃长度是 0 , 所以永远不可能到达最后一个下标。

提示:

  • 1 <= nums.length <= 3 * 104
  • 0 <= nums[i] <= 105
我的解法

有点被大标题误导了只往DP上想(说明当代年轻人真容易被带节奏)

还是要坚持自己的思考啊

class Solution {
public:
    bool canJump(vector& nums) {
        int n=nums.size();
        vector dp(n,false);
        dp[0]=true;
        int i,j;
        for(i=1;i=0;j--)
            {
                if(i-j<=nums[j])
                {
                    dp[i]=true;
                    break;
                }
            }
            if(j==-1)
                break;
        }
        return dp[n-1];
    }
};
官解
class Solution {
public:
    bool canJump(vector& nums) {
        int n = nums.size();
        int rightmost = 0;
        for (int i = 0; i < n; ++i) {
            if (i <= rightmost) {
                rightmost = max(rightmost, i + nums[i]);
                if (rightmost >= n - 1) {
                    return true;
                }
            }
            else
                return false;
        }
        //return false;//防止报错,实际根本不可能来到这里
    }
};

数学

50. Pow(x, n)

实现 pow(x, n) ,即计算 x 的整数 n 次幂函数(即,xn )。

示例 1:

输入:x = 2.00000, n = 10
输出:1024.00000

示例 2:

输入:x = 2.10000, n = 3
输出:9.26100

示例 3:

输入:x = 2.00000, n = -2
输出:0.25000
解释:2-2 = 1/22 = 1/4 = 0.25

提示:

  • -100.0 < x < 100.0
  • -231 <= n <= 231-1
  • -104 <= xn <= 104
解法

快速幂乘法O(logn):解说

【力扣刷题笔记】中级算法_第12张图片

class Solution {
public:
    double myPow(double x, int n) {
        if(n==0) return 1;
        long long N=n;
        int sign;
        if(n<0)
        {
            N=-N;
            sign=-1;
        }
        else
        {
            sign=1;
        }
        double contribute=x;
        double ans=1;
        while(N!=0)
        {
            if(N%2) ans*=contribute;
            contribute*=contribute;
            N>>=1;
        }
        return sign==1?ans:1.0/ans;
    }
};

29. 两数相除

难度中等961

给定两个整数,被除数 dividend 和除数 divisor。将两数相除,要求不使用乘法、除法和 mod 运算符。

返回被除数 dividend 除以除数 divisor 得到的商。

整数除法的结果应当截去(truncate)其小数部分,例如:truncate(8.345) = 8 以及 truncate(-2.7335) = -2

示例 1:

输入: dividend = 10, divisor = 3
输出: 3
解释: 10/3 = truncate(3.33333..) = truncate(3) = 3

示例 2:

输入: dividend = 7, divisor = -3
输出: -2
解释: 7/-3 = truncate(-2.33333..) = -2

提示:

  • 被除数和除数均为 32 位有符号整数。

  • 除数不为 0。

  • 假设我们的环境只能存储 32 位有符号整数,其数值范围是 [−231, 231 − 1]。本题中,如果除法结果溢出,则返回 231 − 1。

解法

关键:类二分查找

【力扣刷题笔记】中级算法_第13张图片

class Solution {
public:
    int divide(int dividend, int divisor) {
        if(dividend==-2147483648)
        {
            if(divisor==-1)
                return 2147483647;
            if(divisor==1)
                return -2147483648;
        }

        if(divisor==-2147483648)
        {
            return dividend==-2147483648?1:0;
        }

        bool rev = false;
        if (dividend > 0) {
            dividend = -dividend;
            rev = !rev;
        }
        if (divisor > 0) {
            divisor = -divisor;
            rev = !rev;
        }
		//写法一:预先计算好
        vector candidates = {divisor};
        while (candidates.back() >= dividend - candidates.back()) {// 注意溢出
            candidates.push_back(candidates.back() + candidates.back());
        }
        int ans = 0;
        for (int i = candidates.size() - 1; i >= 0; --i) {
            if (candidates[i] >= dividend) {
                ans += (1 << i);//2^i
                dividend -= candidates[i];
            }
        }
        
        //写法二:循环中计算
		int ans = 0;
        while(dividend<=divisor)
        {
            int c=divisor,d=1;
            while(c>=dividend-c)//防止溢出,woc学习了
            {
                c+=c;d+=d;
            }
            dividend-=c;
            ans+=d;
        }
        
        
        return rev ? -ans : ans;
    }
};

166. 分数到小数

难度中等406

给定两个整数,分别表示分数的分子 numerator 和分母 denominator,以 字符串形式返回小数

如果小数部分为循环小数,则将循环的部分括在括号内。

如果存在多个答案,只需返回 任意一个

对于所有给定的输入,保证 答案字符串的长度小于 104

示例 1:

输入:numerator = 1, denominator = 2
输出:"0.5"

示例 2:

输入:numerator = 2, denominator = 1
输出:"2"

示例 3:

输入:numerator = 4, denominator = 333
输出:"0.(012)"

提示:

  • -231 <= numerator, denominator <= 231 - 1

  • denominator != 0

解法
class Solution {
public:
    string fractionToDecimal(int numerator, int denominator) {
        long numeratorLong = numerator;
        long denominatorLong = denominator;
        if (numeratorLong % denominatorLong == 0) {
            return to_string(numeratorLong / denominatorLong);
        }

        string ans;
        if (numeratorLong < 0 ^ denominatorLong < 0) {
            ans.push_back('-');
        }

        // 整数部分
        numeratorLong = abs(numeratorLong);
        denominatorLong = abs(denominatorLong);
        long integerPart = numeratorLong / denominatorLong;
        ans += to_string(integerPart);
        ans.push_back('.');

        // 小数部分
        string fractionPart;
        unordered_map remainderIndexMap;
        long remainder = numeratorLong % denominatorLong;
        int index = 0;
        while (remainder != 0 && !remainderIndexMap.count(remainder)) {
            remainderIndexMap[remainder] = index;
            remainder *= 10;
            fractionPart += to_string(remainder / denominatorLong);
            remainder %= denominatorLong;
            index++;
        }
        if (remainder != 0) { // 有循环节
            int insertIndex = remainderIndexMap[remainder];
            fractionPart = fractionPart.substr(0,insertIndex) + '(' + fractionPart.substr(insertIndex);
            fractionPart.push_back(')');
        }
        ans += fractionPart;

        return ans;
    }
};

其他

621. 任务调度器

难度中等989

给你一个用字符数组 tasks 表示的 CPU 需要执行的任务列表。其中每个字母表示一种不同种类的任务。任务可以以任意顺序执行,并且每个任务都可以在 1 个单位时间内执行完。在任何一个单位时间,CPU 可以完成一个任务,或者处于待命状态。

然而,两个 相同种类 的任务之间必须有长度为整数 n 的冷却时间,因此至少有连续 n 个单位时间内 CPU 在执行不同的任务,或者在待命状态。

你需要计算完成所有任务所需要的 最短时间

示例 1:

输入:tasks = ["A","A","A","B","B","B"], n = 2
输出:8
解释:A -> B -> (待命) -> A -> B -> (待命) -> A -> B
     在本示例中,两个相同类型任务之间必须间隔长度为 n = 2 的冷却时间,而执行一个任务只需要一个单位时间,所以中间出现了(待命)状态。 

示例 2:

输入:tasks = ["A","A","A","B","B","B"], n = 0
输出:6
解释:在这种情况下,任何大小为 6 的排列都可以满足要求,因为 n = 0
["A","A","A","B","B","B"]
["A","B","A","B","A","B"]
["B","B","B","A","A","A"]
...
诸如此类

示例 3:

输入:tasks = ["A","A","A","A","A","A","B","C","D","E","F","G"], n = 2
输出:16
解释:一种可能的解决方案是:
     A -> B -> C -> A -> D -> E -> A -> F -> G -> A -> (待命) -> (待命) -> A -> (待命) -> (待命) -> A

提示:

  • 1 <= task.length <= 104
  • tasks[i] 是大写英文字母
  • n 的取值范围为 [0, 100]
解法

https://leetcode.cn/problems/task-scheduler/solution/tong-zi-by-popopop/

int leastInterval(vector& tasks, int n) {
        int len=tasks.size();
        vector vec(26);
        for(char c:tasks) ++vec[c-'A'];
        sort(vec.begin(),vec.end(),[](int& x,int&y){return x>y;});
        int cnt=1;
        while(cnt

总结

做中级的时间明显比初级的时间长了不少

一方面是题目确实更有有难度和思维性,另一方面是这几天出去旅游各种玩没沉下心来

继续努力

你可能感兴趣的:(算法,算法,leetcode)