leetcode hot100

leetcode100 练习

刷题笔记!!!

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/add-two-numbers
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

1.两数之和

给定一个整数数组 nums 和一个目标值 target,请你在该数组中找出和为目标值的那 两个 整数,并返回他们的数组下标。

你可以假设每种输入只会对应一个答案。但是,你不能重复利用这个数组中同样的元素。

给定 nums = [2, 7, 11, 15], target = 9
因为 nums[0] + nums[1] = 2 + 7 = 9
所以返回 [0, 1]
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/two-sum
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

思路: 一开始看到这道题就是暴力,暴力不知道时间和空间够不够, 因此不写了,想了一会儿, 可以哈希一下,map[num[i]] = i, 用一个map来存储数据的值 和 下标, 这样遍历一遍就可以知道, 有没有 target - num[i] 对应的值, 如果有, 可以直接map到;

class Solution {
public:
    vector twoSum(vector& nums, int target) {
        // 数字  map 下标
        vector ans;
        map a;
        for(int i = 0; i < nums.size(); i++){
            a[nums[i]] = i;
        }
        for(int i = 0 ; i < nums.size(); i++){
            int temp = target - nums[i];
            if( a[temp] != 0){
                if(a[temp] != i){
                    ans.push_back(i);
                    ans.push_back(a[temp]);
                    break;
                }
            }
        }
        return ans;
    }
};

2.两数相加

给出两个 非空 的链表用来表示两个非负的整数。其中,它们各自的位数是按照 逆序 的方式存储的,并且它们的每个节点只能存储 一位 数字。

如果,我们将这两个数相加起来,则会返回一个新的链表来表示它们的和。

您可以假设除了数字 0 之外,这两个数都不会以 0 开头。

输入:(2 -> 4 -> 3) + (5 -> 6 -> 4)
输出:7 -> 0 -> 8
原因:342 + 465 = 807

解题思路:

刚拿到这道题的时候,想都没想,直接就遍历两条链表, 求出值, 然后再加起来, 结果是,给出了一个大于9位的数值,直接就超出了int的范围,看来我还是太年轻了,幼稚!!

然后看了下正确的题解, 遍历链表, 一位一位求, 像人工算超长的算法一样!

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {      
        ListNode* ans = new ListNode(0);
        ListNode* index = ans;
        //l l1 中的值; r l2 中的值;  a 进位
        int l, r, a;
        l = r = a = 0;
        //只要有一个不是空的就计算
        while(l1 != NULL || l2 != NULL){
            
            l = l1 == NULL ? 0 : l1->val;
            r = l2 == NULL ? 0 : l2->val;
            //计算每一位的和, 一开始a为0,没有进位
            int sum = l + r + a;    
            sum -= sum>= 10 ? 10 : 0; //大于10的话减去10 进位
            ListNode* temp = new ListNode(sum);
            a = l + r + a>= 10 ? 1 : 0;
            //指向答案下一个节点
            index->next = temp;
            index = temp;
            //响应移动
            if(l1 != NULL)   l1 = l1->next;
            if(l2 != NULL)   l2 = l2->next;
        }
        if(a == 1){
             ListNode* temp = new ListNode(1);
             index->next = temp;
        }
        return ans->next;
    }
};

3.无重复字符的最长子串

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

输入: "abcabcbb"
输出: 3 
解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3
输入: "bbbbb"
输出: 1
解释: 因为无重复字符的最长子串是 "b",所以其长度为 1
输入: "pwwkew"
输出: 3
解释: 因为无重复字符的最长子串是 "wke",所以其长度为 3

​ 请注意,你的答案必须是 子串 的长度,“pwke” 是一个子序列,不是子串。

思路:

一开始没考虑全题目的意思!!!

class Solution {
public:
    int lengthOfLongestSubstring(string s) {
        set<char>  c;
        int ans = 0;
        for(int i = 0; i < s.length(); i++){
            if(c.find(s[i]) != c.end()){
                ans = c.size() > ans? c.size() : ans;
                c.clear();               
            }
             c.insert(s[i]);    
        }
        ans = c.size() > ans? c.size() : ans; 
        return ans;
    }
};

想都不用想肯定错了!

这道题的正确思路是滑动窗口的解题思想, 确保右边的“窗口”一定是没有重复的字母, 每次增加一个字符, 更新窗口的值,在这个过程中记录出现的最大值即为解。

class Solution {
public:
    int lengthOfLongestSubstring(string s) {
        
        // 滑动窗口 start 窗口的开始
        int start = 0, max = 0, j;
        for(int i = 0 ; i < s.length(); i ++){// 每当加入一个字符
            for(j = start; j < i; j ++){  // 遍历新的窗口
                if(s[i] == s[j]){
                     start = j + 1;			//找到第一个相等的字符
                     break;
                }
            }
            if(i - start + 1 > max)
                max = i - start  + 1;
        }
        return max;

    }
};

4.寻找两个有序数组的中位数todo

给定两个大小为 m 和 n 的有序数组 nums1 和 nums2。请你找出这两个有序数组的中位数,并且要求算法的时间复杂度为 O(log(m + n))。

你可以假设 nums1 和 nums2 不会同时为空。

nums1 = [1, 3]
nums2 = [2]
则中位数是 2.0
nums1 = [1, 2]
nums2 = [3, 4]
则中位数是 (2 + 3)/2 = 2.5

//todo 没弄懂

5.最长回文子串

给定一个字符串 s,找到 s 中最长的回文子串。你可以假设 s 的最大长度为 1000。

输入: "babad"
输出: "bab"
注意: "aba" 也是一个有效答案。
输入: "cbbd"
输出: "bb"

解题思路:

题解一: 中心查找

遍历一遍字符串, 如果当前的字符不对称就开始检查,现在是否为最大的回文串, 是就更新!

class Solution {
public:
    string longestPalindrome(string s) {
        int len = s.length();
        int size = len * 2 - 1;//一共有 2*n - 1 个重点
        int max = 0;
        int maxi = 0;
        for(int i = 0; i < size; i++){
            if(i % 2 == 0){//字符本身为中点
                int mid = i / 2;
                int j = 0;
                while(mid - j >= 0 && mid + j < len
                && s[mid-j] == s[mid+j]) j++;
                maxi = 2 * j - 1 > max ? i : maxi;
                max = 2 * j - 1 > max ? 2 * j - 1 : max;
             cout << max << " " << maxi << " " << j << " " << maxi/ 2 <<  " *0*" << i << endl;  
            }
            else if(i % 2 == 1){//不是以字符本身为中点
                int left = i / 2;
                int right = left + 1;
                int j = 0;
                while(left - j >= 0 && right + j < len && 
                s[left - j] == s[right + j]) j++;
                maxi = 2 * j > max ? i : maxi;
                max = 2 * j > max ? 2 * j : max;   
               cout << max << " " << maxi << " " << j << " " << maxi/ 2 <<  " *1*" << i << endl;         
            }
        }
        cout << max << " " << maxi <<endl;
        if(maxi % 2 == 0){
            return s.substr(maxi / 2 - (max/2), max);
        } else return s.substr(maxi / 2 - max/2 +1, max);
    }
};

题解二: 动态规划

p[ i ] [ j ] 表示 i 到 j 为回文串!

递推式:

p[i+1] [j+1] = p[ i ] [ j ] + s[i+1] [j+1];

p[i] [j] = (i == j - 1 ) && s[i] [j - 1];

class Solution {
public:
    string longestPalindrome(string s) {
        if(s == "") return "";
        int len = s.length();
        bool flag[len][len] = {0};
        int max, l;
        l = max = 0;     
        for(int i = 0; i < len; i++)
            for(int j = 0; j < len; j++)
                flag[i][j] = 0;
        for(int i = 0 ; i < len; i ++)  flag[i][i] = 1;
        for(int i = 0 ; i < len; i++){      
            for(int j = 0; j < i; j++){
                 if(j == i - 1 && s[j] == s[i]){
                    flag[j][i] = 1;
                    if(  max < i - j ) max = i - j , l = j;
                 } 
                 if(j - 1 >= 0 && flag[j][i-1] && s[j-1] == s[i]){
                    flag[j-1][i] = 1;
                    if(  max < i - (j - 1) ) max = i - j + 1, l = j-1;
                 }
            }
        }
        return s.substr(l,max+1);
    }
};

6. Z字型变换

将一个给定字符串根据给定的行数,以从上往下、从左到右进行 Z 字形排列。

比如输入字符串为 “LEETCODEISHIRING” 行数为 3 时,排列如下:

L C I R
E T O E S I I G
E D H N
之后,你的输出需要从左往右逐行读取,产生出一个新的字符串,比如:“LCIRETOESIIGEDHN”。

请你实现这个将字符串进行指定行数变换的函数:

string convert(string s, int numRows);

输入: s = "LEETCODEISHIRING", numRows = 3
输出: "LCIRETOESIIGEDHN"
输入: s = "LEETCODEISHIRING", numRows = 4
输出: "LDREOEIIECIHNTSG"

解题思路:

这道题首先想到的是使用找规律, 规律其实很好找!

看到题解中有一个很不错的思路,是把这个的过程直接转换成代码的形式, 好多的题都是这样的!

之前不知道这种做法, 真的是孤陋寡闻!!!

class Solution {
public:
	string convert(string s, int numRows) {

		if (numRows == 1) return s;
		// 这个表示每一行的字符
		vector<string> rows(min(numRows, int(s.size()))); // 防止s的长度小于行数
		int curRow = 0;
		bool goingDown = false;

		for (char c : s) {
			rows[curRow] += c;
			if (curRow == 0 || curRow == numRows - 1) {// 当前行curRow为0或numRows -1时,箭头发生反向转
				goingDown = !goingDown;
			}
			curRow += goingDown ? 1 : -1;
		}
		string ret;
		for (string row : rows) {// 从上到下遍历行
			ret += row;
		}
		return ret;
	}
};

7.整数反转

给出一个 32 位的有符号整数,你需要将这个整数中每位上的数字进行反转。

输入: 123
输出: 321
输入: -123
输出: -321

注意:

假设我们的环境只能存储得下 32 位的有符号整数,则其数值范围为 [−231, 231 − 1]。请根据这个假设,如果反转后整数溢出那么就返回 0。

思路:

这道题最重要的是判断是否溢出, 用一个更大的来存, 或者直接判断(笨方法)。

请你记住下面这两个的求法!!!

MAX_INT = (unsigned)(-1)>>1;
MIN_INT = ~MAX_INT;

或者

MIN_INT = 1 << 31;

MAX_INT = ~MAX_INT;

class Solution {
public:
    int reverse(int x) {
        //首次找到最大的int值
        int MAX_INT, MIN_INT;
        MAX_INT = (unsigned)(-1)>>1;
        MIN_INT = ~MAX_INT;
        long ans = 0;;
        while(x){
            ans = ans * 10 + x % 10;
            x /= 10;
        }
        if(ans > MAX_INT ||  ans < MIN_INT) return 0;
        else return (int)ans;
    }
};

8.字符串转换整数 (atoi)

请你来实现一个 atoi 函数,使其能将字符串转换成整数。

  • 首先,该函数会根据需要丢弃无用的开头空格字符,直到寻找到第一个非空格的字符为止。

  • 当我们寻找到的第一个非空字符为正或者负号时,则将该符号与之后面尽可能多的连续数字组合起来,作为该整数的正负号;假如第一个非空字符是数字,则直接将其与之后连续的数字字符组合起来,形成整数。

  • 该字符串除了有效的整数部分之后也可能会存在多余的字符,这些字符可以被忽略,它们对于函数不应该造成影响。

  • 注意:假如该字符串中的第一个非空格字符不是一个有效整数字符、字符串为空或字符串仅包含空白字符时,则你的函数不需要进行转换。

  • 在任何情况下,若函数不能进行有效的转换时,请返回 0。

说明:

假设我们的环境只能存储 32 位大小的有符号整数,那么其数值范围为 [−2^31, 2^31 − 1]。如果数值超过这个范围,请返回 INT_MAX (2^31 − 1) 或 INT_MIN (−2^31) 。

输入: "42"
输出: 42
输入: "   -42"
输出: -42
输入: "4193 with words"
输出: 4193
输入: "words and 987"
输出: 0
输入: "-91283472332"
输出: -2147483648

解题思路: 直接暴力, 暴力求解!!!

class Solution {

#define INT_MIN (1<<31)
#define INT_MAX ~INT_MIN

public:
    int myAtoi(string str) {
        bool firstnum = 0;
        bool f = 0;
        int  len = str.length();
        long ans = 0;
        for(int i = 0; i < len; i++){       
             if(!firstnum){
                 if(str[i] == ' ') continue;
                 else if(!isdigit(str[i]) && !(str[i] == '-' || str[i] == '+'))
                     return 0;               
                 else if(str[i] == '-' || str[i] == '+'){                  
                    f = str[i] == '-'?1:0;
                 }else ans = ans * 10 + (str[i] - '0');             
                firstnum = 1;           
              }else if(isdigit(str[i])){
                ans = ans * 10 + (str[i] - '0');
                if(f && (ans > INT_MAX )) return INT_MIN;
                else if(ans > INT_MAX) return INT_MAX;
              }else break;
       }
       return f ? ~(int)ans + 1:(int)ans;
    }
};

9.回文数

判断一个整数是否是回文数。回文数是指正序(从左向右)和倒序(从右向左)读都是一样的整数。

输入: 121
输出: true
输入: -121
输出: false
输入: 10
输出: false

你能不将整数转为字符串来解决这个问题吗?

我就直接用字符串处理解决这道题了,还可以用数字反转处理!!

class Solution {
public:
    bool isPalindrome(int x) {
        stringstream ss;
        ss << x;
        string temp;
        ss >> temp;
        for(int i = 0 ; i < temp.length() / 2; i++)
            if(temp[i] != temp[temp.length() - i - 1]) return false;
        return true;
    }
};

10.正则表达式匹配

给你一个字符串 s 和一个字符规律 p,请你来实现一个支持 ‘.’ 和 ‘*’ 的正则表达式匹配。

‘.’ 匹配任意单个字符
‘*’ 匹配零个或多个前面的那一个元素
所谓匹配,是要涵盖 整个 字符串 s的,而不是部分字符串。

说明:

s 可能为空,且只包含从 a-z 的小写字母。
p 可能为空,且只包含从 a-z 的小写字母,以及字符 . 和 *。

s = "aa"
p = "a"
输出: false
解释: "a" 无法匹配 "aa" 整个字符串。
s = "aa"
p = "a*"
输出: true
解释: 因为 '*' 代表可以匹配零个或多个前面的那一个元素, 在这里前面的元素就是 'a'。
     因此,字符串 "aa" 可被视为 'a' 重复了一次。
s = "ab"
p = ".*"
输出: true
解释: ".*" 表示可匹配零个或多个('*')任意字符('.')。
s = "aab"
p = "c*a*b"
输出: true
解释: 因为 '*' 表示零个或多个,这里 'c'0, 'a' 被重复一次。因此可以匹配字符串 "aab"
s = "mississippi"
p = "mis*is*p*."
输出: false

这道题利用的是递归的思想, 我是一步一步增加考虑的情况,

我下面的代码其实可以合并, 合并后可以更加简洁,跟题解是一样的,但我认为初次做这道题, 一下子合并代码的话不容易理解!!

具体的思路都在注释里面!!

class Solution {
public:
    bool isMatch(string s, string p) {
        //这是消除相同的闭包, 不然会超时, 比如a*a*a*a*a*其实一个a*和很多个是没有区别的!!
       // 但是处理的情况却多了很多, 比如第一个a* 会考虑 直接匹配a 也可以不匹配a让后面的匹配,
        // 每一个a* 都是这样考虑这就是一个指数函数了!!!
        if(p.length() > 4){
            for(int i = 0; i < p.length(); i++){
                if(p[i+1] == '*' && i + 3 <  p.length() 
                && p[i+3] =='*' && p[i] == p[i+2]){
                    p = p.substr(0, i) + p.substr(i + 2, p.length() - i - 2);
                }
            }
        }
        cout << "s :  " << s << "   " ;
        cout << " P : " << p <<endl <<endl;
        
        // 如果pattern 空了, 但是s还没有空的话肯定错了
        if(p == "")   return s == "" ? true : false;  
        // 如果字符空了, 但是p还没空, 那就删除一个闭包,因为闭包可以为空,
        // 比如 s == “” p == “a*” 这个是可以匹配的!
        if(s == "") return p.length() >= 2 && p[1] == '*' ?  
            isMatch(s,p.substr(2,p.length() - 2)) : false;
		
        //第一种情况, 一个 . 的情况
        if(p[0] == '.' && (p.length() >=2 && p[1] != '*' || p.length() == 1)){
            return isMatch(s.substr(1,s.length() - 1),p.substr(1,p.length() - 1));
        }
        //第二种  .* 的情况, 返回有两种情况 1、闭包为空, 2、闭包不为空
        else if(p[0] == '.' && p.length() >= 2 && p[1] == '*'){
            return  isMatch(s,p.substr(2,p.length() - 2)) || isMatch(s.substr(1,s.length() - 1),p) ;
        }
        // 第三种情况 字符相同但不是闭包的情况
        else if(p[0] == s[0] && (p.length() >=2 && p[1] != '*' || p.length() == 1) ){
            return isMatch(s.substr(1,s.length() - 1), p.substr(1,p.length() - 1));
        }
        //第四种情况 字符相同且是闭包的情况
        // 返回也有两种: 1、 闭包为空 2、 闭包吃掉一个字符
        else if(p[0] == s[0] && (p.length() >=2 && p[1] == '*')){
            return isMatch(s,p.substr(2,p.length() - 2)) ||isMatch(s.substr(1,s.length() - 1),p);
        }
        // 最后一个情况, 字符不相同, 但是p有闭包, 闭包可以为空
        else if(p[0] != s[0] && p.length() >=2 && p[1] == '*'){
            return isMatch(s,p.substr(2,p.length() - 2));
        }
        // 其它情况的话就是匹配不了的了,返回false
        return false;
    }
};

11.盛最多水的容器

给定 n 个非负整数 a1,a2,…,an,每个数代表坐标中的一个点 (i, ai) 。在坐标内画 n 条垂直线,垂直线 i 的两个端点分别为 (i, ai) 和 (i, 0)。找出其中的两条线,使得它们与 x hyj轴共同构成的容器可以容纳最多的水。

说明:你不能倾斜容器,且 n 的值至少为 2。

img

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

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

首先思路简单:直接暴力可以通过!

class Solution {
public:
    int min(int a, int b){
        return a < b? a : b;
    }

    int maxArea(vector<int>& height) {
        int len = height.size();
        int ans = 0;
        for(int i  = 0 ; i < len; i++ ){
            for(int j = 0; j < i; j ++ ){
                int temp = (i - j) * min(height[i],height[j]);
                ans = temp > ans ? temp : ans;
            }
        }
        return ans;
    }
};

当这道题肯定没有这么简单就过了, 暴力应该不是出题人的本意!

leetco的题解上抄的! Terry su

简单反证法证明:通过双指针方法,两个指针一定会同时经过最大面积对应的指针位置。

假设mn是最大的面积

                  |                     
           |      |                     
           |      |                     
     ......|......|......               
——————————————————————————————————————— 
           m      n

假设有条边p更高在外面,

        |                                
        |          |                     
        |   |      |                     
        |   |      |                     
     ...|...|......|......               
——————————————————————————————————————— 
        p   m      n
  AreaMN = ( n - m ) * min( arr[ m ], arr[ n ] )
  AreaPN = ( n - p ) * min( arr[ p ], arr[ n ] )
 (  n - m ) <= ( n - p )
  min( arr[ m ], arr[ n ] ) <= min( arr[ p ], arr[ n ] )

所以: AreaMN < AreaPN, 与m和n构成最大面积相矛盾,所以假设不成立,即m左侧的点都不高于n,即等于或矮于n。同理可证,n右侧指针等于或矮于m。所以通过双指针方法,两个指针一定会同时经过最大面积对应的指针位置。

原理明白了, 代码自然简单!

class Solution {
public:
    int min(int a, int b){
        return a > b? b : a;
    }
    int maxArea(vector<int>& height) {
        int l = 0, r = height.size() - 1;
        int max = 0;

        while(l < r){
            int temp = (r - l) * min(height[l],height[r]);
            max = max < temp? temp : max;
            if(height[l] < height[r]) l++;
            else r--;
        }
        return max;
    }
};

12.整数转罗马数字

罗马数字包含以下七种字符: 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 到 3999 的范围内。
输入: 3
输出: "III"

这道是我做过简答的, 相当于找钱,每次找最大面额的钱!

我应该是做过类似的题, 不然我不会一看到代码就直接会思路了, 这应该就是刷题的好处了!!!

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

13. 罗马数字转整数

罗马数字包含以下七种字符: 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 到 3999 的范围内。

输入: "III"
输出: 3

解题思路: 12 题反过来, 顺序遍历字符串, 全部加起来, 如果前面的比后面的小要双倍减去,一看代码就明白了!!

class Solution {
public:
    int romanToInt(string s) {
        map<char, int> m;
        m['I'] = 1;
        m['V'] = 5;
        m['X'] = 10;
        m['L'] = 50;
        m['C'] = 100;
        m['D'] = 500;
        m['M'] = 1000;
        int ans = 0;
        for(int i = 0; i < s.length(); i++){
            if(!i){
                ans += m[s[i]];continue;
            } 
            else if(m[s[i]] > m[s[i - 1]]){
                ans -= 2 * m[s[i - 1]]; // 加倍奉还
            }
            ans+= m[s[i]];
        }
        return ans;
    }
};

14.最长公共子串

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

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

输入: ["flower","flow","flight"]
输出: "fl"
输入: ["dog","racecar","car"]
输出: ""

暴力,^_~

class Solution {
public:
    string longestCommonPrefix(vector<string>& strs) {
        if(strs.size() == 0)  return "";
        string ans;
        char com;
        ans = "";
        int index = 0, flag = 0;
        while(true){
            for(int i = 0 ; i < strs.size(); i++){
                string temp = strs[i];
                if(temp.length() == 0) return "";
                if(!i) com = temp[index];
                else if(com  !=  temp[index])
                    return ans;               
                if(index == temp.length() - 1) flag = 1;
            }
            cout << ans <<endl;     
            ans += com;     
            if(flag) return ans;
            index++;
        }              
        return ans;
    }
};

15. 三数之和

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

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

解题思路:

首先这题可以变成一个两数之和题目, 然后一个for循环找nums 里的一个数等于这个两数之和;

所以这道题首先要会两数之和的做法:

两数之和等于0(特例=0)

先将数组排序, 然后利用双指针一个在左边, 一个在右边, 如果指针对应的两个数的和大于0, 则右边指针减1,小于则左边加, 直到相遇, 如果相遇了还没有找到答案就没有解, 这个算法容易,但是怎么证明?

想了很久很久。。。。。。

证明:

首先这个算法有三种情况

  • 和等于0

    随机一个指针往里面移动,比如(-2 -1 1 2) 现在有两个答案, 找到等于0的 -2 2 后, 假设2左移到1,之后 -2 + 1 < 0, 因此左边 右移, -1 + 1 这个答案也找到了!!

  • 和小于0, 左指针右移

  • 和大于0, 右指针左移

现在考虑后面这两种情况, 只有两种选择, 因此这两个指针一定会移到答案(如果有)的其中一个因子上!

上面这句话其实好理解, 因为如果有答案, 算法的停止条件是left < right, 已经遍历了所有的项了!

再举个例子: 两数之和等于 16

-8 -6 5 7 11 17 22(答案可以看出 5  11)

现在再来考虑两种情况:

1、假设 右指针 移到 11(刚好这道题是),

那么此时无论左边指针移到哪里, 反正就是小于5, 那么条件为真的只有左指针右移,直到找到答案!

2、 假设 左指针 移到 5

那么此时无论右指针在哪里, 反正就是大于11,条件为真的只有右指针左移,直至找到答案或相遇!

3、一次只移动左指针或右指针, 不会同时移到答案的两个, 也就是 5 和 11!!

综上上面四点(包括一定会一个先移到答案), 可以证明, 如果有答案一定可以找到这个答案!!

三数之和就好解了!!!看注释就会了!!!

给定数组 nums = [-1, 0, 1, 2, -1, -4],
满足要求的三元组集合为:
[
  [-1, 0, 1],
  [-1, -1, 2]
]
class Solution {
public:
    vector<vector<int>> threeSum(vector<int>& nums) {
        int len = nums.size();
        vector<vector<int> > ans;
        sort(nums.begin(),nums.end());
        int le, ri,sum;
		
        for(int i = 0; i < len; i++){
            le = i + 1;//不重头开始是为了 去重1
            ri = len - 1;      
            if(nums[i] > 0) break;  //这个也是 去重1, 具体自己想
            
            // 如果 第三项 作为 两数之和的相反数 相同, 去重2
            if(i > 0 && nums[i] == nums[i-1]) continue;
            while(le < ri){
                sum = nums[i] + nums[le] + nums[ri];
                if(sum == 0){
                    vector<int> a = {nums[le],nums[i],nums[ri]};
                    ans.push_back(a);   
                    //下面两个 去重2!!!
                    while(le < ri  && nums[le] == nums[le + 1]) le++;
                    while(le < ri  && nums[ri] == nums[ri - 1]) ri--;     
                }                       
                if(sum > 0) ri--;
                else le++; // 这个包含相同时的移动
            }
        }
        return ans;
    }
};

//去重1    a + b + c = 0  如果不去重, a作为 i 得到一个答案, b作为i得到一个答案, c作为i得到一个答案
//去重2    数组中含有相同的数子     a1 + b + c = 0    a2 + b + c = 0

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VJecgIQP-1579447649661)(images/image-20200114220202937.png)]

16.最接近的三数之和

给定一个包括 n 个整数的数组 nums 和 一个目标值 target。找出 nums 中的三个整数,使得它们的和与 target 最接近。返回这三个数的和。假定每组输入只存在唯一答案。

给定数组 nums = [-121-4], 和 target = 1.

与 target 最接近的三个数的和为 2. (-1 + 2 + 1 = 2).

思路就是和三数之和一样不同的是多一个判断!!

class Solution {
public:
    int threeSumClosest(vector<int>& nums, int target) {
        int ans = 0;
        int num = ~(1 << 31);
        int len = nums.size();
        sort(nums.begin(), nums.end());
        int le, ri;
        for(int i = 0; i < len; i++){
            if(i > 0 && nums[i] == nums[i-1]) continue;
            le = i + 1;
            ri = len - 1;    
            while(le < ri){
                int sum = nums[i] + nums[le] + nums[ri] ;   
                //while(le < ri && nums[le] == nums[le+1]) le++;
                //while(le < ri && nums[ri] == nums[ri-1]) ri--;       
                // cout << "num: " << num << " sum: " << sum << " " << i << endl;    
                if(sum ==  target) return target; //已经最进了
                else if(sum < target){
                    le++;
                    if(num >=  target - sum){
                        num = target -sum;
                        ans = sum;
                    }
                }else {
                    ri--;
                    if(num >= sum - target){
                        num = sum - target;
                        ans = sum;
                    }                 
                }                 
            }
             //cout << "num: " << num << " i: " << i << " *" << endl;
        }      
        return ans;
    }
};

17. 电话号码的字母组合

给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。

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

leetcode hot100_第1张图片

输入:"23"
输出:["ad", "ae", "af", "bd", "be", "bf", "cd", "ce", "cf"].
说明:
尽管上面的答案是按字典序排列的,但是你可以任意选择答案输出的顺序。

我用的是广搜!这道搞人的地方是 :

string str = "cao";

string str2 = str[0] + ""; //不行
string str3 = 'a' + 'b'; //不行
class Solution {
    map<char, string> m;  

    
public:
    Solution(){
        m['2'] = "abc";
        m['3'] = "def";
        m['4'] = "ghi";
        m['5'] = "jkl";
        m['6'] = "mno";
        m['7'] = "pqrs";
        m['8'] = "tuv";
        m['9'] = "wxyz";
    }
        
    vector<string> letterCombinations(string digits) {     
        
        vector<string> ans, next;
        if(digits == "") return ans;
        string str = m[digits[0]];
        if(digits.length() > 1)
            next = letterCombinations(digits.substr(1, digits.length() - 1));
        for(int i = 0; i < str.length(); i ++){
            if(digits.length() > 1)
                for(int j = 0; j < next.size(); j ++)
                    ans.push_back(str[i] + next[j]);     
            else    ans.push_back(str.substr(i,1));
        }
        return ans;
    }
};

18. 做不出

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

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

给定一个链表: 1->2->3->4->5, 和 n = 2.
当删除了倒数第二个节点后,链表变为 1->2->3->5.

说明:

给定的 n 保证是有效的。

进阶:

你能尝试使用一趟扫描实现吗?使用hashmap

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode* removeNthFromEnd(ListNode* head, int n) {
        ListNode* first= new ListNode(0);
        ListNode* second = new ListNode(0);
        if(head->next == NULL) return NULL;
        first = second = head;
        while( first->next != NULL &&  n-- ) first = first->next;
        if(n == 1) return head->next;
        while(first->next != NULL)
            first = first->next, second = second->next;
        second->next = second->next->next;
        return head;
    }
};

20.有效的括号

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

有效字符串需满足:

左括号必须用相同类型的右括号闭合。
左括号必须以正确的顺序闭合。
注意空字符串可被认为是有效字符串。

输入: "()"
输出: true
输入: "()[]{}"
输出: true

思路:
使用一个栈来存就行了!

class Solution {
public:
    bool isValid(string s) {

        stack<char> st;
        
        for(int i = 0; i < s.length(); i++){
                if(s[i] == ')' ) {
                    if(st.size() == 0 ||st.top() != '(')  return false;
                    else st.pop();
                }else if( s[i] == ']' ) {
                    if(st.size() == 0 || st.top() != '[')  return false;
                    else st.pop();
                }else if( s[i] == '}' ) {
                    if( st.size() == 0 || st.top() != '{')  return false;
                    else st.pop();
                }else st.push(s[i]);
        }
        if(st.size() == 0) return true;
        return false;

    }
};

21.合并两个有序链表

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

输入:1->2->4, 1->3->4
输出:1->1->2->3->4->4

思路没什么思路, 就是归并排序的最后一步!!! 相当于又写了一遍归并排序!

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
        
        ListNode* ans = new ListNode(0);
        ListNode* index = new ListNode(0);
        index = ans;
		//一步一步想!!!!
        while(l1 != NULL || l2 != NULL){
            if(l2 == NULL ) index->next = l1, l1 = l1->next;
            else if(l1 == NULL ) index->next = l2, l2 = l2->next;
            else if(l1->val < l2->val) index->next = l1, l1 = l1->next;
            else index->next = l2, l2 = l2->next;
            index = index->next;
        }
        return ans->next;
    }
};

简化后的代码:

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
     
        ListNode* ans = new ListNode(0);
        ListNode* index = new ListNode(0);
        index = ans;
        while(l1 != NULL || l2 != NULL){
            if(l2 == NULL ) index->next = l1, l1 = l1->next;
            //下面代码 l2 肯定不为空了, l1为空了 就直接跳过后面的了, 所以可以直接合并
            else if(l1 == NULL || l1->val >= l2->val ) index->next = l2, l2 = l2->next;
            else index->next = l1, l1 = l1->next;
            index = index->next;
        }
        return ans->next;
    }
};

22.有效括号

给出 n 代表生成括号的对数,请你写出一个函数,使其能够生成所有可能的并且有效的括号组合。

例如,给出 n = 3,生成结果为:

[
  "((()))",
  "(()())",
  "(())()",
  "()(())",
  "()()()"
]

思路: 一开始我想到的是广搜, 广搜虽好,但是会超内存, 因为要一个栈来存储答案, 于时就改成了深搜,深搜一定要剪枝!

广搜如果保留太多的信息会让内存超出限制

class Solution {
public:
    vector<string> generateParenthesis(int n) {
        vector<string> ans = {"("};
        return  bfs(ans,1,0,n);
    }
    vector<string>  bfs(vector<string> m, int left, int right, int len){    
        vector<string> ans; 
        if(left < right) return ans;
        if(left >= len && right == len) return ans;
        if(left >= right){
            vector<string> temp = m;
            if(left > right){          
                for(int j = 0; j < temp.size(); j++) temp[j] += ")";
                temp = bfs(temp,left, right+1,len);
            }       
            for(int i = 0; i < m.size(); i++) m[i] += "(";
            ans =  bfs(m,left +1 , right,len);
            if(temp.size() != 0) 
                for(int i = 0; i < temp.size(); i++)
                    ans.push_back(temp[i]);
        }
        return m;
    }
};

改一下不用临时变量, 还他么超了?

class Solution {
public:
    vector<string> generateParenthesis(int n) {
        vector<string> ans;
        bfs(ans,1,0,n,"(");
        return  ans;
    }
    void  bfs(vector<string>& ans, int left, int right, int len, string cur){    
        if(left < right) return;
        if(left >= len && right == len){
            ans.push_back(cur);
            return ;
        } 
        if(left >= right){
            if(left != right)  
                bfs(ans,left, right+1, len,cur + ")");
            bfs(ans,left+1,right, len, cur + "(");
        }
        return;
    }
};

再改!!

只有改dfs了, dfs的剪枝很重要

class Solution {
public:
    vector<string> generateParenthesis(int n) {
        vector<string> ans;
        dfs(ans,n,"(");
        return  ans;
    }
    void  dfs(vector<string>& ans, int len, string cur){    
        if(!valid(len, cur)) return;
        if(valid(len, cur) == 1)
             ans.push_back(cur); 
        dfs(ans, len, cur+ "(");
        dfs(ans, len, cur+ ")");
    }
    int valid(int len, string cur){
        int left, right;
        right = left = 0;
        for(int i = 0; i < cur.length(); i++){
            if(cur[i] == '(') left++;
            else right++;
            //剪枝
            if(left > len  || left < right) return 0;
        }
        if(left == len && right == len) return 1;
        return 2;
    }

};

23.合并K个排序链表

合并 k 个排序链表,返回合并后的排序链表。请分析和描述算法的复杂度。

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

解题思路, 每次将链表合并到一个总的链表上

时间复杂度分析:

前两个合并, 一共有n个节点,评分 n/k 个节点每条,

前两条合并为 n*2 / k , 之后每次合并都要加上之前的

x + x + 2x + x + 3x + x + 4x + x + …(k-1)x + x = kx + k^2 / 2 x = k^2 * x = k ^2 * (n*2/k) = kx

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode* mergeKLists(vector<ListNode*>& lists) {
        if(lists.size() == 0) return NULL;
        ListNode* ans = new ListNode(0); 
        for(int i = 0; i < lists.size(); i++){
            if(!i) ans = lists[i];
            else {
                ListNode* head = new ListNode(0);
                ListNode* index = new ListNode(0);
                head = index;
                while(lists[i] != NULL || ans!= NULL){
                        if(lists[i] == NULL){
                            index->next = ans;
                            break;
                        }else if(ans == NULL){
                            index->next = lists[i];
                            break;
                        }else if(ans->val >= lists[i]->val){
                            index->next = lists[i];
                            lists[i] = lists[i]->next;
                        }else{
                            index->next = ans;
                            ans = ans->next;
                        }
                        index = index->next;
                }
                ans = head->next;
            }      
        }
        return ans;
    }
};

24.两两交换链表 todo

给定一个链表,两两交换其中相邻的节点,并返回交换后的链表。

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

示例:

给定 1->2->3->4, 你应该返回 2->1->4->3.

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
     ListNode* swapPairs(ListNode* head) {
        ListNode *res = new ListNode(0),*tmp = res;
        res->next = head;
        while(tmp->next != NULL && tmp->next->next != NULL) {
            ListNode *start = tmp->next,*end = tmp->next->next;
            tmp->next = end;
            start->next = end->next;
            end->next = start;
            tmp = start;
        }
        return res->next;
    }

};

25.k个一组翻转链表

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

k 是一个正整数,它的值小于或等于链表的长度。

如果节点总数不是 k 的整数倍,那么请将最后剩余的节点保持原有顺序。

给定这个链表:1->2->3->4->5

当 k = 2 时,应当返回: 2->1->4->3->5

当 k = 3 时,应当返回: 3->2->1->4->5

说明 :

你的算法只能使用常数的额外空间。
你不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。

思路: 其实就是暴力

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode* reverseKGroup(ListNode* head, int k) {   
        ListNode * index = head;   
        bool flag = 0;
        for(int i = 0; i < k; i ++ ){
            if(index == NULL) break;
            else index = index->next;
            if(i == k -1 )  flag = 1;  
        }
        ListNode* pre = NULL;
        ListNode* next = NULL;
        ListNode* temp = head;
        if(flag){       
            while(head != index){
                next = head->next;
                head->next = pre;
                pre = head;
                head = next;
            }
            temp->next = reverseKGroup(index,k);
        }else {
            pre = head;
        }    
        return pre;
    }
};

26. 下一个排列

123的全排列为

123 132 213 231 312 321

123的下一排列为132, 321没有所以为123

直接思路:

从后面往前找,先找出最大的索引 k 满足 nums[k] < nums[k+1],如果不存在,就翻转整个数组;
从后面往前找,再找出另一个最大索引 l 满足 nums[l] > nums[k];一定有一个的
交换 nums[l] 和 nums[k];
最后翻转 nums[k+1:]。

思路: 这道题主要理解排列的意思, 翻译过来就是上面的解法,

class Solution {
public:
    void nextPermutation(vector<int>& nums) {
        if(nums.size() <= 1) return;
        for(int i = nums.size() - 1 ; i >= 0; i--){
            if(!i) reverse(nums, 0, nums.size() - 1);
            //第一个前面大的
            if(i && nums[i] > nums[i-1]){
                for(int j = nums.size() - 1; j >= 0; j --){
                    if(nums[j] > nums[i - 1]){
                        int temp = nums[j];
                        nums[j] = nums[ i - 1];
                        nums[i - 1] = temp;
                        reverse(nums,i,nums.size() - 1);
                        return ;
                    }
                }

            }
        }
    }

    void reverse(vector<int>& nums,int start, int end){
       int temp;
       for(int i = start; i <= (end + start) / 2; i++){
           temp = nums[i];
           nums[i] = nums[end - i + start];
           nums[end - i + start]  = temp;
       }
    }
};

27. 最长有效括号

给定一个只包含 ‘(’ 和 ‘)’ 的字符串,找出最长的包含有效括号的子串的长度。

输入: "(()"
输出: 2
解释: 最长有效括号子串为 "()"

解题思路:

从左边向右遍历, 如果当前的左括号小于右括号证明是没有用的,从新开始计数, 如果当前的左括号等于右括号是一个有效值,判断是否为最大值, 右边一样!

class Solution {
public:
    int longestValidParentheses(string s) {
        int ri, le, ans;
        ri = le = ans = 0;   
        for(int i = 0 ; i < s.length(); i++){
            if(s[i] == '(') le++;
            else ri++;
            if(le == ri) ans = ans > le * 2 ? ans : le * 2;
            else if(le < ri) le = ri = 0;
        }
        ri = le = 0;
        for(int i = s.length() - 1; i >= 0; i--){
            if(s[i] == '(') le++;
            else ri++;
            if(le == ri) ans = ans > le * 2 ? ans : le * 2;
            else if(le > ri) le = ri = 0;
        }
        return ans;
    }
};

28. 搜索旋转排序数组

假设按照升序排序的数组在预先未知的某个点上进行了旋转。

( 例如,数组 [0,1,2,4,5,6,7] 可能变为 [4,5,6,7,0,1,2] )。

搜索一个给定的目标值,如果数组中存在这个目标值,则返回它的索引,否则返回 -1 。

你可以假设数组中不存在重复的元素。

你的算法时间复杂度必须是 O(log n) 级别。

输入: nums = [4,5,6,7,0,1,2], target = 0
输出: 4

解题思路:

这题主要理解的问题是,一个旋转之后的数组,拆成两半, 总有一个半是有效的!

class Solution {
public:
    int search(vector<int>& nums, int target) {
        if(nums.size() == 0) return -1;
        return devideSearch(nums, 0, nums.size() - 1, target);
    }

    int devideSearch(vector<int>& nums, int le, int ri , int target){
        
        if(ri == le) return nums[ri] == target? ri : -1;
        // if(ri - le == 1){
        //     if(nums[ri] == target) return  ri;
        //     else if( nums[le] == target) return le;
        //     return -1;
        // }        
        int mid = (le + ri) / 2;
       // cout << le << " " << ri << " " << mid << endl;
        // 左边有序
        if(nums[le] < nums[mid] ){
            if( nums[le] <= target && nums[mid] >= target)
                return devideSearch(nums, le, mid,target);
            else return devideSearch(nums, mid + 1, ri,target);
            
        }else {
            if(nums[mid + 1] <= target && nums[ri] >= target)
                return devideSearch(nums,mid + 1, ri,target);
            else return devideSearch(nums, le, mid,target);            
        }
    }



};

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

给定一个按照升序排列的整数数组 nums,和一个目标值 target。找出给定目标值在数组中的开始位置和结束位置。

你的算法时间复杂度必须是 O(log n) 级别。

如果数组中不存在目标值,返回 [-1, -1]。

输入: nums = [5,7,7,8,8,10], target = 8
输出: [3,4]
输入: nums = [5,7,7,8,8,10], target = 6
输出: [-1,-1]

解题思路:
这道题关键还是二分搜索, 当搜索到答案以后, 往两边找相同的!!!!

class Solution {
public:
    vector<int> searchRange(vector<int>& nums, int target) {
        if(nums.size()==0) {
             vector<int> temp = {-1,-1};
            return temp;
        }
        vector<int> ans;
      
        int mid = search(nums, 0, nums.size() - 1, target);
        if(mid == -1 ) {
            ans.push_back(mid);
            ans.push_back(mid);
            return ans;
        } 
        else {
            int i;
            for(i = mid; i >= 0; i--){
                if(nums[i] != target) break; 
            }
            ans.push_back(i+1);
            for(i = mid; i < nums.size(); i++){
                if(nums[i] != target) break; 
            }
            ans.push_back(i-1);  
        }
        return ans;
    }

    int search(vector<int>& nums, int le, int ri, int target){
        if(target < nums[0] || target > nums[ri]) return -1;
        if(ri == le) return target == nums[le] ? le : -1;
        int mid = (le + ri) / 2;
        if(nums[mid] < target) return search(nums,mid + 1, ri, target);
        else return search(nums,le, mid, target);
    
    }

};

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

给定一个排序数组,你需要在原地删除重复出现的元素,使得每个元素只出现一次,返回移除后数组的新长度。

不要使用额外的数组空间,你必须在原地修改输入数组并在使用 O(1) 额外空间的条件下完成。

给定数组 nums = [1,1,2], 

函数应该返回新的长度 2, 并且原数组 nums 的前两个元素被修改为 1, 2。 

你不需要考虑数组中超出新长度后面的元素。

思路: 使用双指针法, 前面的指针判断是否相等!

class Solution {
public:
    int removeDuplicates(vector<int>& nums) {
        int guard, ans;
        ans = guard  = 0;
        if(nums.size() == 0) return 0;
        while(true){
            while(guard + 1 < nums.size() && nums[guard] == nums[guard+1])guard++;
            nums[ans++] = nums[guard++];
            if(guard == nums.size()) break;        
        }
        return ans;
    }
};

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