LeetCode11~20题解

LeetCode11.盛水最多的容器:

题目描述:

给定一个长度为 n 的整数数组 height 。有 n 条垂线,第 i 条线的两个端点是 (i, 0) 和 (i, height[i]) 。

找出其中的两条线,使得它们与 x 轴共同构成的容器可以容纳最多的水。

返回容器可以储存的最大水量。

说明:你不能倾斜容器。

示例 1:

输入:[1,8,6,2,5,4,8,3,7]

输出:49

解释:图中垂直线代表输入数组 [1,8,6,2,5,4,8,3,7]。在此情况下,容器能够容纳水(表示为蓝色部分)的最大值为 49。

示例 2:

输入:height = [1,1]

输出:1

提示:

n == height.length

2 <= n <= 10^5

0 <= height[i] <= 10^4

思路:

贪心思想:使用双指针分别从头和尾开始,每次计算比较两个指针的高度,将较小的往中间移一个位置,每次保留最大的装水体积

证明:

代码:

复制代码
class Solution {
public:
    int maxArea(vector<int>& height) {
        int res = 0;
        for(int i = 0, j = height.size() - 1; i < j; )
        {
            res = max(res, min(height[i], height[j]) * (j - i));
            if(height[i] > height[j]) j--;
            else i++;
        }
        return res;
    }
};

LeetCode12.整数转罗马数字:

题目描述:

七个不同的符号代表罗马数字,其值如下:

符号:值

I 1

V 5

X 10

L 50

C 100

D 500

M 1000

罗马数字是通过添加从最高到最低的小数位值的转换而形成的。将小数位值转换为罗马数字有以下规则:

如果该值不是以 4 或 9 开头,请选择可以从输入中减去的最大值的符号,将该符号附加到结果,减去其值,然后将其余部分转换为罗马数字。

如果该值以 4 或 9 开头,使用减法形式,表示从以下符号中减去一个符号,例如 4 是 5 (V) 减 1 (I): IV ,9 是 10 (X) 减 1 (I):IX。仅使用以下减法形式:4 (IV),9 (IX),40 (XL),90 (XC),400 (CD) 和 900 (CM)。

只有 10 的次方(I, X, C, M)最多可以连续附加 3 次以代表 10 的倍数。你不能多次附加 5 (V),50 (L) 或 500 (D)。如果需要将符号附加4次,请使用减法形式。

给定一个整数,将其转换为罗马数字。

示例 1:

输入:num = 3749

输出: "MMMDCCXLIX"

解释:

3000 = MMM 由于 1000 (M) + 1000 (M) + 1000 (M)

700 = DCC 由于 500 (D) + 100 © + 100 ©

40 = XL 由于 50 (L) 减 10 (X)

9 = IX 由于 10 (X) 减 1 (I)

注意:49 不是 50 (L) 减 1 (I) 因为转换是基于小数位

示例 2:

输入:num = 58

输出:"LVIII"

解释:

50 = L

8 = VIII

示例 3:

输入:num = 1994

输出:"MCMXCIV"

解释:

1000 = M

900 = CM

90 = XC

4 = IV

提示:

1 <= num <= 3999

思路: 找规律:

这样的话从高位往下依次减, 只要将圈住的部分用数组存起来,每次找到对应的数值转换成字符串之后,再原有基础上减去当前数值,然后继续往下找能减的部分继续减并转换。

代码:

复制代码
class Solution {
public:
    string intToRoman(int num) {
        int values[] = {
            1000,
            900, 500, 400, 100,
            90, 50, 40, 10,
            9, 5, 4, 1
        };
        string reps[] = {
            "M",
            "CM", "D", "CD", "C",
            "XC", "L", "XL", "X",
            "IX", "V", "IV", "I"
        };
        string res;
        for(int i = 0; i < 13; i++)
        {
            while(num >= values[i])
            {
                num -= values[i];
                res += reps[i];
            }
        }
        return res;
    }
};

LeetCode13. 罗马数字转整数:

题目描述:

罗马数字包含以下七种字符: I, V, X, L,C,D 和 M。

字符 数值

I 1

V 5

X 10

L 50

C 100

D 500

M 1000

例如, 罗马数字 2 写做 II ,即为两个并列的 1 。12 写做 XII ,即为 X + II 。 27 写做 XXVII, 即为 XX + V + II 。

通常情况下,罗马数字中小的数字在大的数字的右边。但也存在特例,例如 4 不写做 IIII,而是 IV。数字 1 在数字 5 的左边,所表示的数等于大数 5 减小数 1 得到的数值 4 。同样地,数字 9 表示为 IX。这个特殊的规则只适用于以下六种情况:

I 可以放在 V (5) 和 X (10) 的左边,来表示 4 和 9。

X 可以放在 L (50) 和 C (100) 的左边,来表示 40 和 90。

C 可以放在 D (500) 和 M (1000) 的左边,来表示 400 和 900。

给定一个罗马数字,将其转换成整数。

示例 1:

输入: s = "III"

输出: 3

示例 2:

输入: s = "IV"

输出: 4

示例 3:

输入: s = "IX"

输出: 9

示例 4:

输入: s = "LVIII"

输出: 58

解释: L = 50, V= 5, III = 3.

示例 5:

输入: s = "MCMXCIV"

输出: 1994

解释: M = 1000, CM = 900, XC = 90, IV = 4.

提示:

1 <= s.length <= 15

s 仅含字符 ('I', 'V', 'X', 'L', 'C', 'D', 'M')

题目数据保证 s 是一个有效的罗马数字,且表示整数在范围 [1, 3999] 内

题目所给测试用例皆符合罗马数字书写规则,不会出现跨位等情况。

IL 和 IM 这样的例子并不符合题目要求,49 应该写作 XLIX,999 应该写作 CMXCIX 。

关于罗马数字的详尽书写规则,可以参考 罗马数字 - 百度百科。

思路:

找潜在规律可以发现:在I, V,X, L,C, D, M这些基本的罗马数字之间

只要前面的罗马字符代表的数值大于当前代表的数值,就可以直接将当前字符代表的数值加到后面,而 IV, IX, XL, XC, CD, CM这几个罗马数字可以发现,前面的单独字符都比后面的字符所代表的数值小,而后者代表数值减去前者代表数值刚好为它本身代表的数值,所以总结发现,我们只需要判断前后两个字符单独代表的数值大小,就能直接对每个字符进行加减运算

实现方式:将单独的罗马字符以及它表示的数值映射到hash表中,依次遍历罗马数字的每个字符,找到hash表中对应的数值,对比前后两个字符表示的数值,进行加减即可。

代码:

复制代码
class Solution {
public:
    int romanToInt(string s) {
        unordered_map<char, int> hash;
        hash['I'] = 1, hash['V'] = 5;
        hash['X'] = 10, hash['L'] = 50;
        hash['C'] = 100, hash['D'] = 500;
        hash['M'] = 1000;

        int res = 0;
        for(int i = 0; i < s.size(); i++)
        {
            //如果i的后面有字符,并且代表的数值小于后面的字符代表数值
            if(i + 1 < s.size() && hash[s[i]] < hash[s[i + 1]])
            {
                //那么减去代表数值
                res -= hash[s[i]];
            }
            //否则加上代表数值
            else  res += hash[s[i]];
        }
        return res;
    }
};

纯享版:

复制代码
class Solution {
public:
    int romanToInt(string s) {
        unordered_map<char, int> hash;
        hash['I'] = 1, hash['V'] = 5;
        hash['X'] = 10, hash['L'] = 50;
        hash['C'] = 100, hash['D'] = 500;
        hash['M'] = 1000;
        int ans = 0;
        for(int i = 0; i < s.size(); i++)
        {
            if(i + 1 < s.size() && hash[s[i]] < hash[s[i + 1]])
            {
                ans -= hash[s[i]];
            }
            else ans += hash[s[i]];
        }
        return ans;
    }
};

LeetCode14.最长公共前缀:

题目描述:

编写一个函数来查找字符串数组中的最长公共前缀。

如果不存在公共前缀,返回空字符串 ""。

示例 1:

输入:strs = ["flower","flow","flight"]

输出:"fl"

示例 2:

输入:strs = ["dog","racecar","car"]

输出:""

解释:输入不存在公共前缀。

提示:

1 <= strs.length <= 200

0 <= strs[i].length <= 200

strs[i] 仅由小写英文字母组成

思路:

从第一个字符开始依次对比每个字符串的每个字符,全能比对上就将当前字符加入答案子串中

注意:

这里加&符是为了减少时间消耗,否则的话每次循环复制一遍整个数组,时间复杂度会上升

代码:

复制代码
class Solution {
public:
    string longestCommonPrefix(vector<string>& strs) {
        string res;
        if(strs.empty()) return res;
        //从第一个字符开始遍历
        for(int i = 0; i <= 200; i++)
        {
            //如果i大于第一个字符串的长度了,那么后面也就没必要继续了
            if(i >= strs[0].size()) return res;
            //每次取第一个字符串的第i个字符
            char c = strs[0][i];
            for(auto& str : strs)
            {
                //跟每一个字符串进行比对,如果字符串长度小于i那么肯定不用继续了,或者说第i个字符匹配不上
                if(str.size() <= i || str[i] != c)
                {
                    return res;
                }
            }
            //能比对上就在答案子串里将当前字符加上
            res += c;
        }
        return res;
    }
};

纯享版:

复制代码
class Solution {
public:
    string longestCommonPrefix(vector<string>& strs) {
        string res;
        if(strs.empty()) return res;

        for(int i = 0; i <= 200; i++)
        {
            if(i >= strs[0].size()) return res;
            char c = strs[0][i];
            for(auto& str : strs)
            {
                if(str.size() <= i || str[i] != c)
                {
                    return res;
                }
            }
            res += c;
        }
        return res;
    }

LeetCode15.三数之和:

题目描述:

给你一个整数数组 nums ,判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i != j、i != k 且 j != k ,同时还满足 nums[i] + nums[j] + nums[k] == 0 。请你返回所有和为 0 且不重复的三元组。

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

示例 1:

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

输出:[[-1,-1,2],[-1,0,1]]

解释:

nums[0] + nums[1] + nums[2] = (-1) + 0 + 1 = 0 。

nums[1] + nums[2] + nums[4] = 0 + 1 + (-1) = 0 。

nums[0] + nums[3] + nums[4] = (-1) + 2 + (-1) = 0 。

不同的三元组是 [-1,0,1] 和 [-1,-1,2] 。

注意,输出的顺序和三元组的顺序并不重要。

示例 2:

输入:nums = [0,1,1]

输出:[]

解释:唯一可能的三元组和不为 0 。

示例 3:

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

输出:[[0,0,0]]

解释:唯一可能的三元组和为 0 。

提示:

3 <= nums.length <= 3000

-105 <= nums[i] <= 105

思路:

先从小到大排序,然后先固定最小的那个数nums[i],再使用双指针将后面的两个数确定下来,第二个数nums[j]从第一个数nums[i]后面开始,第三个数nums[k]从最后面开始,每次找到能满足nums[i] + nums[j] + nums[k] >= 0最小的nums[k]三个数的组合,过程中会发现当nums[j]往后遍历时,满足条件的nums[k]会往前挪,两者(j ,k)都具有单调性,使用双指针算法可以极大优化时间复杂度,将原本O(n ^2)变为O(2n)时间复杂度。

代码:

复制代码
class Solution {
public:
    vector<vector<int>> threeSum(vector<int>& nums) {
        vector<vector<int>> res;
        //先进行排序
        sort(nums.begin(), nums.end());
        //先将第一个数固定下来
        for(int i = 0; i < nums.size(); i++)
        {
            //避免找到重复的方案, 因为已经排序了,nums[i]也固定了, 后面两个数肯定比nums[i]大
            //而且nums[i]的方案后面已经全部考虑了
            if(i && nums[i] == nums[i - 1]) continue;
            //双指针算法:  可以发现,j 从i开始的话,只会逐渐增大, 而k从后往前开始会逐渐减小,具有单调性,使用双指针算法可以极大优化时间复杂度,将原本O(N ^2)变为O(2n)
            //j:表示第二个数, 从i后面开始找
            //k: 表示第三个数,从最后面往前找
            //j < k : 两个指针不能重叠
            for(int j = i + 1, k = nums.size() - 1; j < k; j++)
            {
                //这里的j和i一样需要排除重复方案
                if(j > i + 1 && nums[j] == nums[j - 1]) continue;
                //找到三个数相加大于零的最小的k
                //如果k的下一个数没有和j重叠,并且三个数之和大于零就往前挪一位,
                //因为从小到大排序了,所以越往前挪越接近0
                while(j < k - 1 && nums[i] + nums[j] + nums[k - 1] >= 0) k--;
                //看三个数之和是否满足条件,满足就将三个数存到容器中
                if(nums[i] + nums[j] + nums[k] == 0)
                {
                    res.push_back({nums[i], nums[j], nums[k]});
                }
            }
            
        }
        return res;
    }
};

纯享版:

复制代码
class Solution {
public:
    vector<vector<int>> threeSum(vector<int>& nums) {
        vector<vector<int>> res;
        sort(nums.begin(), nums.end());
        for(int i = 0; i < nums.size(); i++)
        {
            if(i && nums[i] == nums[i - 1]) continue;
            for(int j = i + 1, k = nums.size() - 1; j < k; j++)
            {
                if(j > i + 1 && nums[j] == nums[j - 1]) continue;
                while(j < k - 1 && nums[i] + nums[j] + nums[k - 1] >= 0) k--;
                if(nums[i] + nums[j] + nums[k] == 0)
                {
                    res.push_back({nums[i], nums[j], nums[k]});
                }
            }
        }
        return res;
    }
};

###LeetCode16.最接近的三数之和:

##题目描述:

给你一个长度为 n 的整数数组 nums 和 一个目标值 target。请你从 nums 中选出三个整数,使它们的和与 target 最接近。

返回这三个数的和。

假定每组输入只存在恰好一个解。

示例 1:

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

输出:2

解释:与 target 最接近的和是 2 (-1 + 2 + 1 = 2)。

示例 2:

输入:nums = [0,0,0], target = 1

输出:0

解释:与 target 最接近的和是 0(0 + 0 + 0 = 0)。

提示:

3 <= nums.length <= 1000

-1000 <= nums[i] <= 1000

-10^4 <= target <= 10^4

##思路:这里跟LeetCode15题思路一致,不过要注意细节方面处理,要考虑最接近target的和,那么有可能是小于target也有可能是大于target,因此要将三数之和与target的差值进行比较,留下最小差的和,

##代码:

复制代码
class Solution {
public:
    int threeSumClosest(vector<int>& nums, int target) {
        sort(nums.begin(), nums.end());
        pair<int, int> res(INT_MAX, INT_MAX);
        for(int i = 0; i < nums.size(); i++)
        {
            for(int j = i + 1, k = nums.size() - 1; j < k; j++)
            {
                while(k - 1 > j && nums[i] + nums[j] + nums[k - 1] >= target) k--;
                //第一种是nums[i] + nums[j] + nums[k] >= target 的情况
                int s = nums[i] + nums[j] + nums[k];
                //记录最接近target的和,这里有可能会出现所有和都小于target的情况,所以使用abs
                res = min(res, make_pair(abs(s - target), s));
                //nums[i] + nums[j] + nums[k] < target 的情况
                //因为第一种情况将nums[k] 逼到三个数最靠近target的情况,
                //如果nums[k] + nums[i] + nums[j] 是最接近大于等于target的,那么nums[k - 1] + nums[i] + nums[j] 肯定是小于target的
                if(k - 1 > j)
                {
                    s = nums[i]  + nums[j] + nums[k - 1];
                    res = min(res, make_pair(target - s, s));
                }
            }
        }
        return res.second;
    }
};

##纯享版:

复制代码
class Solution {
public:
    int threeSumClosest(vector<int>& nums, int target) {
        sort(nums.begin(), nums.end());
        pair<int, int> res(INT_MAX, INT_MAX);
        for(int i = 0; i < nums.size(); i++)
        {
            for(int j = i + 1, k = nums.size() - 1; j < k; j++)
            {
                while(j < k - 1 && nums[i] + nums[j] + nums[k - 1] >= target) k--;
                int s = nums[i] + nums[j] + nums[k];
                res = min(res, make_pair(abs(s - target), s));
                if(j < k - 1)
                {
                    int s = nums[i] + nums[j] + nums[k - 1];
                    res = min(res, make_pair(target - s, s));
                }
            }
        }
        return res.second;
    }
};

LeetCode17.电话号码的数字组合:

题目描述:

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

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

示例 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算法:深度优先遍历

##注意:只要是涉及到排列组合问题,只要能找到对应的排列数,先考虑dfs能不能做。

代码:

复制代码
class Solution {
public:
vector<string> ans;
string strs[10] = {
    "", "", "abc", "def", 
    "ghi", "jkl", "mno",
     "pqrs", "tuv", "wxyz"
};
    vector<string> letterCombinations(string digits) {
        if(digits.empty()) return ans;
        dfs(digits, 0, "");
        return ans;       
    }

    void dfs(string&digits, int u, string path)
    {
        //path存方案:表示每次的路径
        //u表示当前是第几位
        //如果u == digits.size() 说明已经排列好了, 将当前走的路径放入ans里
        if(u == digits.size()) ans.push_back(path);
        else{
            //取出当前位代表的每一个字符, 分别递归下去,直到将所有位全部排完
            for(auto c : strs[digits[u] - '0'])
            {
                dfs(digits, u + 1, path + c);
            }
        }
    }
};

纯享版:

复制代码
class Solution {
public:
    vector<string> res;
    string strs[10] = {
        "", "", "abc",  "def",
        "ghi", "jkl", "mno",
        "pqrs", "tuv", "wxyz"
    };
    vector<string> letterCombinations(string digits) {
        if(digits.empty()) return res;
        dfs(digits, 0, "");
        return res;
    }

    void dfs(string& digits, int u, string path)
    {
        if(u == digits.size()) res.push_back(path);
        else{
            for(auto c : strs[digits[u] - '0'])
            {
                dfs(digits, u + 1, path + c);
            }
        }
    }
};

LeetCode18.四数之和:

题目描述:

给你一个由 n 个整数组成的数组 nums ,和一个目标值 target 。请你找出并返回满足下述全部条件且不重复的四元组 [nums[a], nums[b], nums[c], nums[d]] (若两个四元组元素一一对应,则认为两个四元组重复):

0 <= a, b, c, d < n

a、b、c 和 d 互不相同

nums[a] + nums[b] + nums[c] + nums[d] == target

你可以按 任意顺序 返回答案 。

示例 1:

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

输出:[[-2,-1,1,2],[-2,0,0,2],[-1,0,0,1]]

示例 2:

输入:nums = [2,2,2,2,2], target = 8

输出:[[2,2,2,2]]

提示:

1 <= nums.length <= 200

-10^9 <= nums[i] <= 10^9

-10^9 <= target <= 10^9

思路:

这里思路跟三数之和一模一样,只不过使用三层循环,同样使用双指针优化,时间复杂度为O(n^2 * 2n)

注意:

这里数值范围会超过int的取值范围, 所以要使用long long 进行存储

代码:

复制代码
class Solution {
public:
    typedef long long LL;
    vector<vector<int>> fourSum(vector<int>& nums, int target) {
        vector<vector<int> >  res;
        sort(nums.begin(), nums.end());
        LL num = target;
        for(int i = 0; i < nums.size(); i++)
        {
            //cout<<i<<endl;
            if(i && nums[i] == nums[i - 1])  continue;
            for(int j = i + 1; j < nums.size(); j++)
            {
                //cout<<j<<endl;
                if(j > i + 1 && nums[j] == nums[j - 1]) continue;
                for(int k = j + 1, u = nums.size() - 1; k < u; k++)
                {
                    
                    if(k > j + 1 && nums[k] == nums[k - 1]) continue;
                    
                    while(k < u - 1 && (LL)(nums[i] + nums[j]) + (LL)(nums[k] + nums[u - 1]) >= num) u--;
                    
                    if((LL)(nums[i] + nums[j]) + (LL)(nums[k] + nums[u]) == num)
                    {
                        res.push_back({nums[i], nums[j], nums[k], nums[u]});
                    }
                }
            }
        }
        return res;
    }
};

纯享版:

复制代码

LeetCode19.删除链表的倒数第N个节点:

题目描述:

给你一个链表,删除链表的倒数第 n 个结点,并且返回链表的头结点。

示例 1:

输入:head = [1,2,3,4,5], n = 2

输出:[1,2,3,5]

示例 2:

输入:head = [1], n = 1

输出:[]

示例 3:

输入:head = [1,2], n = 1

输出:[1]

提示:

链表中结点的数目为 sz

1 <= sz <= 30

0 <= Node.val <= 100

1 <= n <= sz

##思路:找到链表的倒数第N+1个数,将指针直接指向倒数第N个数的后面,也就是倒数第N+1 个数的next的next

注意:

一般情况下可以直接跳过倒数第N个数,但有些时候需要删除第N个节点

注释代码:

复制代码
/**
 * 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* removeNthFromEnd(ListNode* head, int n) {
        //定义虚拟头节点
        auto dummy = new ListNode(-1);
        //将虚拟头节点连接在原本头节点前
        dummy ->next = head;

        int num = 0;
        //计算链表长度
        for(auto p = dummy; p; p = p->next) num++;
        auto p = dummy;
        //从虚拟头节点开始,跳到倒数 n + 1个数的位置
        for(int i = 0; i < num - n - 1; i++) p = p -> next;
        //让倒数第n + 1个数直接指向后面的后面, 就能直接跳过倒数第 n个数
        p -> next = p -> next -> next;
        //返回链表
        return dummy -> 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:
    ListNode* removeNthFromEnd(ListNode* head, int n) {
        auto dummy = new ListNode(-1);
        dummy -> next = head;

        int num = 0;
        for(auto p = dummy; p; p = p -> next) num++;

        auto p = dummy;
        for(int i = 0; i < num - n - 1; i++) p = p -> next;
        p -> next = p -> next -> next;
        return dummy -> next;
    }
};

LeetCode20.有效的括号:

题目描述:

给定一个只包括 '(',')','{','}','[',']' 的字符串 s ,判断字符串是否有效。

有效字符串需满足:

左括号必须用相同类型的右括号闭合。

左括号必须以正确的顺序闭合。

每个右括号都有一个对应的相同类型的左括号。

示例 1:

输入:s = "()"

输出:true

示例 2:

输入:s = "()[]{}"

输出:true

示例 3:

输入:s = "(]"

输出:false

示例 4:

输入:s = "([])"

输出:true

提示:

1 <= s.length <= 10^4

s 仅由括号 '()[]{}' 组成

思路:

总思路使用栈,每次将左括号入栈,而右括号则进行匹对,如果每个右括号都能在栈顶找到对应的左括号,每次将匹配的栈顶弹出,最后如果栈中没有元素那么说明是有效的。 这里的算法1是使用取巧方法, 查ASCII码值发现,每对括号的差值都不超过2,如果超过说明是不匹配的,那么直接return false 就行。 算法2则是枚举每种右括号的情况,在栈不为空的情况下进行栈顶比对,匹配上则直接将栈顶弹出

算法1:(取巧)代码:

复制代码
class Solution {
public:
    bool isValid(string s) {
        stack<char> stk;
        for(auto c : s)
        {   
            //如果是右括号,那么将它存入栈中
            if(c == '{' || c == '[' || c == '(') stk.push(c);
            else {
                //如果是左括号,那么根据当前字符与栈顶元素的ASCII码值是否小于等于2来判断是否匹配
                //匹配的话将当前栈顶去除
                if(stk.size() && abs(stk.top() - c) <= 2) stk.pop();
                else return false;
            }
        }
        //如果全部能够匹配上,那么栈为空
        return stk.empty();
    }
};

算法2: 枚举情况:

复制代码
class Solution {
public:
    bool isValid(string s) {
        stack<int> stk;
        for(auto c : s)
        {
            if(c == '[' || c == '{' || c == '(') stk.push(c);
            else{
                if(stk.size() && c == ']' && stk.top() == '[') stk.pop();
                else if(stk.size() && c == '}' && stk.top() == '{') stk.pop();
                else if(stk.size() && c == ')' && stk.top() == '(') stk.pop();
                else return false;
            }
        }
        return stk.empty();
    }
};

LeetCode21.合并两个有序链表:

题目描述:

将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。

示例 1:

输入:l1 = [1,2,4], l2 = [1,3,4]

输出:[1,1,2,3,4,4]

示例 2:

输入:l1 = [], l2 = []

输出:[]

示例 3:

输入:l1 = [], l2 = [0]

输出:[0]

提示:

两个链表的节点数目范围是 [0, 50]

-100 <= Node.val <= 100

l1 和 l2 均按 非递减顺序 排列

##注释代码:

方法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* mergeTwoLists(ListNode* l1, ListNode* l2) {
        //定义虚拟头节点
        auto dummy = new ListNode(-1);
        //当前尾节点
        auto tail = dummy;
        //每次将最小值归并到新建链表中
        while(l1 && l2)
        {
            if(l1 -> val > l2 -> val)
            {
                tail -> next = l2;
                tail = tail -> next;
                l2 = l2 -> next;
            } else {
                tail -> next = l1;
                tail = tail -> next;
                l1 = l1 -> next;
            }   
        }
        //这里是将l1和l2剩余部分直接拼接到归并后的链表后面,所以不需要指针再一步一步移动
        if(l1) tail -> next = l1;
        if(l2) tail -> next = l2;
        return dummy -> 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* mergeTwoLists(ListNode* l1, ListNode* l2) {
        //定义虚拟头节点
        auto dummy = new ListNode(-1);
        //当前尾节点
        auto tail = dummy;
        //每次将最小值归并到新建链表中
        while(l1 && l2)
        {
            if(l1 -> val > l2 -> val)
            {
                tail -> next = new ListNode(l2 -> val);
                tail = tail -> next;
                l2 = l2 -> next;
            } else {
                tail -> next = new ListNode(l1 -> val);
                tail = tail -> next;
                l1 = l1 -> next;
            }   
        }
        //这里是将l1和l2剩余部分直接拼接到归并后的链表后面,所以不需要指针再一步一步移动
        if(l1) tail -> next = l1;
        if(l2) tail -> next = l2;
        return dummy -> 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:
    ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
        auto dummy = new ListNode(-1);
        auto tail = dummy;
        while(l1 && l2)
        {
            if(l1 -> val > l2 -> val)
            {
                tail -> next = l2;
                tail = tail -> next;
                l2 = l2 -> next;
            }else{
                tail -> next = l1;
                tail = tail -> next;
                l1 = l1 -> next;
            }
        }
        if(l1) tail -> next = l1;
        if(l2) tail -> next = l2;
        return dummy -> next;
    }
};

LeetCode22.括号生成:

题目描述:

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

示例 1:

输入:n = 3

输出:["((()))","(()())","(())()","()(())","()()()"]

示例 2:

输入:n = 1

输出:["()"]

提示:

1 <= n <= 8

思路:

这里重要的有两步,一是最后看左右括号数量是否相等,二是每次要看左括号的数量是否大于等于右括号的数量,因为在限制条件下,只要左括号的数量小于右括号,就可以通过添加右括号去合法化方案,但是如果有一个地方右括号数量比左括号多就无法在后面进行补齐

代码:

复制代码
class Solution {
public:
    vector<string> res;
    vector<string> generateParenthesis(int n) {
        dfs(n, 0, 0, "");
        return res;
    }
    void dfs(int& n, int lc, int rc, string path)
    {
        if(lc == n && rc == n) res.push_back(path);
        else{
            if(rc < n && lc > rc) dfs(n, lc, rc + 1, path + ')');
            if(lc < n) dfs(n, lc + 1, rc, path + '(');
        }
    }
};

LeetCode23.合并K个排序链表:

题目描述:

给你一个链表数组,每个链表都已经按升序排列。

请你将所有链表合并到一个升序链表中,返回合并后的链表。

示例 1:

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

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

解释:链表数组如下:

1-\>4-\>5, 1-\>3-\>4, 2-\>6

将它们合并到一个有序链表中得到。

1->1->2->3->4->4->5->6

示例 2:

输入:lists = []

输出:[]

示例 3:

输入:lists = [[]]

输出:[]

提示:

k == lists.length

0 <= k <= 10^4

0 <= lists[i].length <= 500

-10^4 <= lists[i][j] <= 10^4

lists[i] 按 升序 排列

lists[i].length 的总和不超过 10^4

思路:

LeetCode21.合并两个有序链表 一样,每次将最小的节点值归并到新的链表中,正常做法是枚举对比每个链表的节点值,依次将最小的归并到新的链表中,这样的话找最小值的时间复杂度是k * n的,但是这里使用优先队列(小根堆)维护最小值,每次插入和删除的时间复杂度为log(k)的,那么n个链表就是n* log(k)

注意:

这里涉及的优先队列(默认大根堆)进行重载成小根堆不懂的参考 Acwing839.模拟堆关于优先队列大小根堆重载操作符的说明

注释代码:

复制代码
/**
 * 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:
    //对STL容器的比较运算符"()"进行重载
    //因为STL容器在比较的时候用的是结构体的小括号运算符
    struct Cmp{
        //这里一般来说默认是大根堆,默认小的在下面
        //大根堆的排序判断是传入less,也就是比较父子节点的值,如果父节点的值小于子节点那么进行交换
        //每次将大的值移到上面,所以最后形成大根堆
        //我们要使用小根堆每次维护最小的节点值,所以要对它的默认判断进行重载
        //重载为greater,每次比较父子节点的值,将小的传到上面,形成小根堆
        bool operator() (ListNode* a, ListNode* b)
        {
            return a -> val > b -> val;
        }
    };
    ListNode* mergeKLists(vector<ListNode*>& lists) {
        priority_queue<ListNode*, vector<ListNode*>, Cmp> heap;
        auto dummy = new ListNode(-1);
        auto tail = dummy;
        //防止传入空链表
        //这里传入的是每个链表头节点的地址
        for(auto l : lists) if(l) heap.push(l);

        while(heap.size())
        {
            //每次取出并去除小根堆的堆顶元素
            auto t = heap.top();
            heap.pop();
            //让当前新链表的尾节点往后移动
            tail -> next = t;
            tail = tail -> next;
            //如果之前堆顶元素的后面还有元素(也就是将堆顶元素所在的链表的下一个节点放入到堆中,重新排序,找出最小元素的链表的头节点)
            if(t -> next) heap.push(t -> next);
        }
        return dummy -> 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:
    struct Cmp
    {
        bool operator() (ListNode* a, ListNode* b)
        {
            return a -> val > b -> val;
        }
    };
    ListNode* mergeKLists(vector<ListNode*>& lists) {
        priority_queue<ListNode*, vector<ListNode*>, Cmp> h;
        auto dummy = new ListNode(-1);
        auto tail = dummy;
        for(auto l : lists) if(l) h.push(l);

        while(h.size())
        {
            auto temp = h.top();
            h.pop();
            tail -> next = temp;
            tail = tail -> next;
            if(temp -> next) h.push(temp -> next);
        }
        return dummy -> next;
    }
};

LeetCode24.两两交换链表中的节点:

题目描述:

给你一个链表,两两交换其中相邻的节点,并返回交换后链表的头节点。你必须在不修改节点内部的值的情况下完成本题(即,只能进行节点交换)。

示例 1:

输入:head = [1,2,3,4]

输出:[2,1,4,3]

示例 2:

输入:head = []

输出:[]

示例 3:

输入:head = [1]

输出:[1]

提示:

链表中节点的数目在范围 [0, 100] 内

0 <= Node.val <= 100

思路:

链表问题惯例定义虚拟头节点,然后接在链表的前面,设立三个指针,维护虚拟头节点和交换的两个节点,虚拟头节点每次移动到要交换的两个节点前面,只要虚拟头节点后面两个节点都存在就进行交换并进行指针移动,这里特别注意的是三个指针之间的移动关系,防止丢失

代码:

复制代码
/**
 * 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) {
        auto dummy = new ListNode(-1);
        dummy -> next = head;
        for(auto p = dummy; p -> next && p -> next -> next;)
        {
            auto a = p -> next, b = a -> next;
            p -> next = b;
            a -> next = b -> next;
            b -> next = a;
            p = a;

        }
        return dummy -> 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:
    ListNode* swapPairs(ListNode* head) {
        auto dummy = new ListNode(-1);
        dummy -> next = head;
        auto p = dummy;
        while(p -> next && p -> next -> next)
        {
            auto a = p -> next, b = a -> next;
            a -> next = b -> next;
            p -> next = b;
            b -> next = a;
            p = a;
        }

        return dummy -> next;
    }
};

###LeetCode25.K个一组翻转链表:

##题目描述:

给你链表的头节点 head ,每 k 个节点一组进行翻转,请你返回修改后的链表。

k 是一个正整数,它的值小于或等于链表的长度。如果节点总数不是 k 的整数倍,那么请将最后剩余的节点保持原有顺序。

你不能只是单纯的改变节点内部的值,而是需要实际进行节点交换。

示例 1:

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

输出:[2,1,4,3,5]

示例 2:

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

输出:[3,2,1,4,5]

提示:

链表中的节点数目为 n

1 <= k <= n <= 5000

0 <= Node.val <= 1000

##思路:题目看起来比较复杂,涉及翻转又要循环,那么为了将复杂事情简单化,思路一般为分步:

##第一步: 每次循环看当前位置的后面够不够k个节点,不足之间跳出

##第二步: 对于k个节点内部进行k-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* reverseKGroup(ListNode* head, int k) {
        auto dummy = new ListNode(-1);
        dummy -> next = head;
        for(auto p = dummy;;)
        {
            auto q = p;
            //先判断p后面够不够k个元素: p往后面跳k步后的元素是否为空
            //保证q不是空节点才往后跳
            for(int i = 0; i < k && q; i++)  q = q -> next;
            if(!q) break;
            //处理内部翻转
            //k个节点总共k-1条边
            auto a = p -> next, b = a -> next;
            for(int i = 0; i < k - 1; i++)
            {
                auto c = b -> next;
                b -> next = a;  //内部反转,将后面的指针指向前面
                //指针错位(迭代)
                a = b, b = c;
            }
            //每次的头尾连接
            auto c = p -> next;
            p -> next = a;
            c -> next = b;
            //将p指针移动到下一次的前一个位置
            p = c;
        }
        return dummy -> 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:
    ListNode* reverseKGroup(ListNode* head, int k) {
        auto dummy = new ListNode(-1);
        dummy -> next = head;
        for(auto p = dummy;;)
        {
            auto temp = p;
            for(int i = 0; i < k && temp; i++) temp = temp -> next;
            if(!temp) break;
            auto a = p -> next, b = a -> next;
            for(int i = 0; i < k - 1; i++)
            {
                auto c = b -> next;
                b -> next = a;
                a = b, b = c;
            }
            auto c = p -> next;
            p -> next = a;
            c -> next = b;
            p = c;
        }
        return dummy -> next;
    }
};

LeetCode26.删除排序数组中的重复项:

题目描述:

给你一个 非严格递增排列 的数组 nums ,请你 原地 删除重复出现的元素,使每个元素 只出现一次 ,返回删除后数组的新长度。元素的 相对顺序 应该保持 一致 。然后返回 nums 中唯一元素的个数。

考虑 nums 的唯一元素的数量为 k ,你需要做以下事情确保你的题解可以被通过:

更改数组 nums ,使 nums 的前 k 个元素包含唯一元素,并按照它们最初在 nums 中出现的顺序排列。nums 的其余元素与 nums 的大小不重要。

返回 k 。

判题标准:

系统会用下面的代码来测试你的题解:

int[] nums = [...]; // 输入数组

int[] expectedNums = [...]; // 长度正确的期望答案

int k = removeDuplicates(nums); // 调用

assert k == expectedNums.length;

for (int i = 0; i < k; i++) {

assert nums[i] == expectedNums[i];

}

如果所有断言都通过,那么您的题解将被 通过。

示例 1:

输入:nums = [1,1,2]

输出:2, nums = [1,2,_]

解释:函数应该返回新的长度 2 ,并且原数组 nums 的前两个元素被修改为 1, 2 。不需要考虑数组中超出新长度后面的元素。

示例 2:

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

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

解释:函数应该返回新的长度 5 , 并且原数组 nums 的前五个元素被修改为 0, 1, 2, 3, 4 。不需要考虑数组中超出新长度后面的元素。

提示:

1 <= nums.length <= 3 * 10^4

-10^4 <= nums[i] <= 10^4

nums 已按 非严格递增 排列

思路:

代码:

复制代码
class Solution {
public:
    int removeDuplicates(vector<int>& nums) {
        int k = 0;
        //k维护每个第一次出现的数组元素
        for(int i = 0 ; i < nums.size(); i++)
        {
            //如果i 不为0且当前元素跟上一个元素不一样
            if(!i || nums[i] != nums[i - 1])
            {
                //将第k个位置替换为当前元素,并且往后移动一位
                nums[k++] = nums[i];
            }
        }
        //最后k的大小就是数组中第一次出现的所有元素个数
        return k;
    }
};

纯享版:

复制代码
class Solution {
public:
    int removeDuplicates(vector<int>& nums) {
        int k = 0;
        for(int i = 0; i < nums.size(); i++)
        {
            if(!i || nums[i] != nums[i - 1])
            {
                nums[k++] = nums[i];
            }
        }
        return k;
    }
};

LeetCode27.移除元素:

题目描述:

给你一个数组 nums 和一个值 val,你需要 原地 移除所有数值等于 val 的元素。元素的顺序可能发生改变。然后返回 nums 中与 val 不同的元素的数量。

假设 nums 中不等于 val 的元素数量为 k,要通过此题,您需要执行以下操作:

更改 nums 数组,使 nums 的前 k 个元素包含不等于 val 的元素。nums 的其余元素和 nums 的大小并不重要。

返回 k。

用户评测:

评测机将使用以下代码测试您的解决方案:

int[] nums = [...]; // 输入数组

int val = ...; // 要移除的值

int[] expectedNums = [...]; // 长度正确的预期答案。

// 它以不等于 val 的值排序。

int k = removeElement(nums, val); // 调用你的实现

assert k == expectedNums.length;

sort(nums, 0, k); // 排序 nums 的前 k 个元素

for (int i = 0; i < actualLength; i++) {

assert nums[i] == expectedNums[i];

}

如果所有的断言都通过,你的解决方案将会 通过。

示例 1:

输入:nums = [3,2,2,3], val = 3

输出:2, nums = [2,2,, ]

解释:你的函数函数应该返回 k = 2, 并且 nums 中的前两个元素均为 2。

你在返回的 k 个元素之外留下了什么并不重要(因此它们并不计入评测)。

示例 2:

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

输出:5, nums = [0,1,4,0,3,, ,_]

解释:你的函数应该返回 k = 5,并且 nums 中的前五个元素为 0,0,1,3,4。

注意这五个元素可以任意顺序返回。

你在返回的 k 个元素之外留下了什么并不重要(因此它们并不计入评测)。

提示:

0 <= nums.length <= 100

0 <= nums[i] <= 50

0 <= val <= 100

思路:

LeetCode26.删除有序数组中的重复项 思路一样

代码:

复制代码
class Solution {
public:
    int removeElement(vector<int>& nums, int val) {
        int k = 0; 
        for(int i = 0; i < nums.size(); i++)
        {
            if(nums[i] != val)
            {
                nums[k++] = nums[i];
            }
        }
        return k;
    }
};

LeetCode28.实现strStr():

题目描述:

给你两个字符串 haystack 和 needle ,请你在 haystack 字符串中找出 needle 字符串的第一个匹配项的下标(下标从 0 开始)。如果 needle 不是 haystack 的一部分,则返回 -1 。

示例 1:

输入:haystack = "sadbutsad", needle = "sad"

输出:0

解释:"sad" 在下标 0 和 6 处匹配。

第一个匹配项的下标是 0 ,所以返回 0 。

示例 2:

输入:haystack = "leetcode", needle = "leeto"

输出:-1

解释:"leeto" 没有在 "leetcode" 中出现,所以返回 -1 。

提示:

1 <= haystack.length, needle.length <= 10^4

haystack 和 needle 仅由小写英文字符组成

算法1:

思路:暴力枚举O(NM)

直接按照定义,从长串的每一个字符开始暴力匹配子串

##时间复杂度: 两重循环,O(nm)

复制代码
class Solution {
public:
    int strStr(string s, string p) {
        int n = s.size(), m = p.size();
        for(int i = 0; i < n - m + 1; i++)
        {
            bool st = true;
            for(int j = 0; j < m; j++)
            {
                
                if(s[i + j] != p[j])
                {
                    st = false;
                    break;
                }
            }
            if(st) return i;
        }
        return -1;
    }
};

算法2:

思路:

创建子串的next数组,当前匹配不上时,下一个可以直接开始比较的位置(前缀和后缀相等的最大长度)

KMP:

时间复杂度: O(n + m)

复制代码
class Solution {
public:
    int strStr(string s, string p) {
        if(p.empty()) return 0;
        int n = s.size(), m = p.size();
        s = ' ' + s, p = ' ' + p;
        
        vector<int> next(m + 1);
        //求next数组
        //其实是自身进行匹配的过程
        //一直循环往复
        for(int i = 2, j = 0; i <= m; i++)
        {
            while(j && p[i] != p[j + 1]) j = next[j];
            //如果此时j的后面一位(前缀:j + 1)跟它原本相同的后缀的后面一位相同,说明此时可以扩展一位
            if(p[i] == p[j + 1]) j++;
            //将最长的前缀和后缀长度放入next数组
            next[i] = j;
        }

        //KMP匹配过程
        for(int i = 1, j = 0; i <= n; i++)
        {
            //如果不匹配了, 将j跳转到next[j]的位置
            //也就是p[1~j]中前缀和后缀相同的最大长度的位置,节省前面next[j]个字符的匹配
            //或者是j退无可退的情况下,从头开始匹配
            while(j && s[i] != p[j + 1]) j = next[j];
            //当后面的一个字符能继续匹配,j往后移动
            if(s[i] == p[j + 1]) j++;
            //如果能完全匹配 m个字符,那么返回第一个匹配字符的下标
            //这里 返回i- m 是因为原本是 i - m + 1, 但是由于把字符串增加了一位,所以变成i -m
            if(j == m) return i - m;
        }
        return -1;
    }
};

纯享版:

复制代码
class Solution {
public:
    int strStr(string s, string p) {
        int n = s.size(), m = p.size();
        if(m == 0) return 0;
        s = ' ' + s, p = ' ' + p;
        vector<int> next(m + 1);
        for(int i = 2, j = 0; i <=  m; i++)
        {
            while(j && p[i] != p[j + 1]) j = next[j];
            if(p[i] == p[j + 1]) j++;
            next[i] = j;
        }

        for(int i = 1, j = 0; i <= n; i++)
        {
            while(j && s[i] != p[j + 1]) j = next[j];
            if(s[i] == p[j + 1]) j++;
            if(j == m) return i - m;
        }
        return -1;
    }
};

LeetCode29.两数相除:

题目描述:

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

整数除法应该向零截断,也就是截去(truncate)其小数部分。例如,8.345 将被截断为 8 ,-2.7335 将被截断至 -2 。

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

注意:假设我们的环境只能存储 32 位 有符号整数,其数值范围是 [−2^31, 2^31 − 1] 。本题中,如果商 严格大于 2^31 − 1 ,则返回 2^31 − 1 ;如果商 严格小于 -2^31 ,则返回 -2^31 。

示例 1:

输入: dividend = 10, divisor = 3

输出: 3

解释: 10/3 = 3.33333... ,向零截断后得到 3 。

示例 2:

输入: dividend = 7, divisor = -3

输出: -2

解释: 7/-3 = -2.33333... ,向零截断后得到 -2 。

提示:

-2^31 <= dividend, divisor <= 2^31 - 1

divisor != 0

思路:

正常暴力做的话就是每次被除数减去除数,但是当被除数为2^31 -1, 除数为1时,需要执行231-1(大约1019)次,肯定超时,所以这里使用类似于快速幂的思想,将商转换成二进制表示,从高往低判断商的当前位是否为1,如果为1就减去当前位,继续判断,这样只要减不超过30次,就能很快确定商的值

注释代码:

复制代码
class Solution {
public:
    int divide(int x, int y) {
        typedef long long LL;
        //exp存指数项
        vector<LL> exp;
        //确定符号, false 表示结果是正数
        bool is_minus = false;
        //如果x, y符号不相同,那么,令符号标记为true
        if(x < 0 && y > 0 || x > 0 && y < 0) is_minus = true;
        //取 x, y 的绝对值,方便计算
        LL a = abs((LL)x), b = abs((LL) y);
        //将b的指数项依次存入exp中
        for(LL i = b; i <= a; i = i + i) exp.push_back(i);

        LL res = 0;
        for(int i = exp.size() - 1; i >= 0; i--)
        {
            if(a >= exp[i])
            {
                //说明商的当前位上应该是1
                a -= exp[i];
                //答案加上当前商
                res += 1ll << i;
            }
        }
        //最后加上符号
        if(is_minus)  res = -res;
        if(res > INT_MAX ||res < INT_MIN) return INT_MAX;
        
        return res;

    }
};

纯享版:

复制代码
class Solution {
public:
    int divide(int x, int y) {
        typedef long long LL;
        vector<LL> exp;
        bool is_minus = false;
        if(x > 0 && y < 0 || x < 0 && y > 0) is_minus = true;
        LL a = abs((LL)x), b = abs((LL)y);
        for(LL i = b; i <= a; i = i + i)  exp.push_back(i);

        LL res = 0;
        for(int i = exp.size() - 1; i >= 0; i--)
        {
            if(a >= exp[i])
            {
                a -= exp[i];
                res += 1ll << i;
            }
        }
        if(is_minus) res = -res;
        if(res > INT_MAX || res < INT_MIN) return INT_MAX;
        return res;
    }
};

LeetCode30.串联所有单词的子串:

题目描述:

给定一个字符串 s 和一个字符串数组 words。 words 中所有字符串 长度相同。

s 中的 串联子串 是指一个包含 words 中所有字符串以任意顺序排列连接起来的子串。

例如,如果 words = ["ab","cd","ef"], 那么 "abcdef", "abefcd","cdabef", "cdefab","efabcd", 和 "efcdab" 都是串联子串。 "acdbef" 不是串联子串,因为他不是任何 words 排列的连接。

返回所有串联子串在 s 中的开始索引。你可以以 任意顺序 返回答案。

示例 1:

输入:s = "barfoothefoobarman", words = ["foo","bar"]

输出:[0,9]

解释:因为 words.length == 2 同时 words[i].length == 3,连接的子字符串的长度必须为 6。

子串 "barfoo" 开始位置是 0。它是 words 中以 ["bar","foo"] 顺序排列的连接。

子串 "foobar" 开始位置是 9。它是 words 中以 ["foo","bar"] 顺序排列的连接。

输出顺序无关紧要。返回 [9,0] 也是可以的。

示例 2:

输入:s = "wordgoodgoodgoodbestword", words = ["word","good","best","word"]

输出:[]

解释:因为 words.length == 4 并且 words[i].length == 4,所以串联子串的长度必须为 16。

s 中没有子串长度为 16 并且等于 words 的任何顺序排列的连接。

所以我们返回一个空数组。

示例 3:

输入:s = "barfoofoobarthefoobarman", words = ["bar","foo","the"]

输出:[6,9,12]

解释:因为 words.length == 3 并且 words[i].length == 3,所以串联子串的长度必须为 9。

子串 "foobarthe" 开始位置是 6。它是 words 中以 ["foo","bar","the"] 顺序排列的连接。

子串 "barthefoo" 开始位置是 9。它是 words 中以 ["bar","the","foo"] 顺序排列的连接。

子串 "thefoobar" 开始位置是 12。它是 words 中以 ["the","foo","bar"] 顺序排列的连接。

提示:

1 <= s.length <= 10^4

1 <= words.length <= 5000

1 <= words[i].length <= 30

words[i] 和 s 由小写英文字母组成

思路:

这里的思路是将s字符串按照words的单词长度w进行区间划分,从0 ~ w-1为起止位置,每次划分w的长度,这样做相当于每一个槽位刚好是单个单词,可以直接将s字符串的所有单词通过巧妙的方式列举出来,然后对于words的单词匹配则使用滑动窗口进行匹配,同时使用cnt记录有效单词的个数,这里的cnt主要作用是记录在当前窗口维护的有效单词,如果有效单词个数等于words单词个数,那么说明这是一种匹配方案

注意:

这里的cnt是有效单词的个数,进入滑动窗口时会根据窗口中的次数跟tot中words单词出现的次数进行判断

注释代码:

复制代码
class Solution {
public:
    vector<int> findSubstring(string s, vector<string>& words) {
        vector<int>res;
        if(words.empty()) return res;
        int n = s.size(), m = words.size(), w = words[0].size();
        //单词池的每个单词出现的次数
        unordered_map<string, int> tot;
        //将每个单词对应出现的次数+1
        for(auto&word : words) tot[word]++;
        //从0 ~w-1 开始按w固定长度划分整个区间
        for(int i = 0; i < w; i++)
        {
            //当前窗口维护的单词
            unordered_map<string, int> wd;
            //cnt : 确定有多少个单词是给定tot集合中的
            //cnt 维护的是有效的单词,如果没有在tot集合中出现过,或者出现次数大于tot集合次数都是被排除在外的   
            int cnt = 0;
            //将整个字符串按固定长度w划分,起始位置从0 ~ w,每次一个槽位刚好对应一个单词
            for(int j = i; j + w <= n; j += w)
            {
                //窗口大小满足m个单词的话
                if(j >= i + m * w)
                {
                    //将最前面放入的单词找出来
                    auto word = s.substr(j - m * w, w);
                    //每次窗口移动到删除最前面放入的单词
                    wd[word]--;
                    //如果删除该单词之后,小于tot中对应出现的次数
                    //说明删除的是有效单词,从cnt中减去一个有效单词
                    //这里的wd无论出现哪个单词,对应出现的次数都是1,最坏的情况都是1
                    //但是,对于tot来说,如果不是有效的单词,那么它出现的次数为0
                    //所以这里只要删除当前单词使得wd的次数小于tot中的次数了,说明删除的是一个有效单词
                    if(wd[word] < tot[word]) cnt--;
                }
                //通过槽位找到下一个单词
                auto word = s.substr(j, w);
                //将该单词加入到到当前窗口
                wd[word]++;
                //如果该单词加入之后,满足tot中应该出现的次数,说明是有效单词
                if(wd[word] <= tot[word]) cnt++;
                //通过这种方式,每次将有效单词计入cnt,当cnt == words中的单词数量,说明能够匹配到相同的字符串
                //将当前的起始位置存入答案
                if(cnt == m) res.push_back(j - (m -1) * w);
            }
        }
        return res;
    }
};

纯享版:

复制代码
class Solution {
public:
    vector<int> findSubstring(string s, vector<string>& words) {
        vector<int> res;
        if(words.empty()) return res;
        int n = s.size(), m = words.size(), w = words[0].size();

        unordered_map<string, int> tot;
        for(auto word : words) tot[word]++;

        for(int i = 0; i < w; i++)
        {
            unordered_map<string, int> wd;
            int cnt = 0;
            for(int j = i; j + w <= n; j += w)
            {
                if(j >= i + m * w)
                {
                    auto word = s.substr(j - m * w,w);
                    wd[word]--;
                    if(wd[word] < tot[word]) cnt--;
                }
                auto word = s.substr(j, w);
                wd[word]++;
                if(wd[word] <= tot[word]) cnt++;
                if(cnt == m) res.push_back(j - (m - 1) * w);
            }
        }
        return res;
    }
};
相关推荐
ZLRRLZ5 分钟前
【数据结构】二叉树进阶算法题
数据结构·c++·算法
闻缺陷则喜何志丹9 分钟前
【并集查找】P4380 [USACO18OPEN] Multiplayer Moo S|省选-
数据结构·c++·洛谷·并集查找
1白天的黑夜130 分钟前
二分查找-153-寻找旋转排序数组中的最小值-力扣(LeetCode)
c++·leetcode·二分查找
pimkle1 小时前
LC 135 分发糖果 JavaScript ts
leetcode·typescript
arin8761 小时前
【图论】倍增与lca
算法·图论
CoovallyAIHub1 小时前
别卷单模态了!YOLO+多模态 才是未来场景实战的“天选方案”
深度学习·算法·计算机视觉
guozhetao2 小时前
【图论,拓扑排序】P1347 排序
数据结构·c++·python·算法·leetcode·图论·1024程序员节
慕雪_mx2 小时前
最短路算法
算法
AI_Keymaker2 小时前
对话DeepMind创始人Hassabis:AGI、宇宙模拟与人类文明的下一个十年
算法
yi.Ist2 小时前
关于二进制的规律
算法·二进制·bitset