LeetCode(个人记录,总结,持续更新...)

这篇博客详细介绍了LeetCode上的经典算法题目,包括两数之和、两数相加、无重复字符的最长子串、寻找两个正序数组的中位数、最长回文子串等多个问题。博主提供了多种解题方法,如直接循环、利用哈希表、滑动窗口等,并分享了代码实现和思路分析。
摘要由CSDN通过智能技术生成

1. 两数之和

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

你可以假设每种输入只会对应一个答案。但是,数组中同一个元素在答案里不能重复出现。

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

示例 1:

输入:nums = [2,7,11,15], target = 9
输出:[0,1]
解释:因为 nums[0] + nums[1] == 9 ,返回 [0, 1] 。

方法一:直接两个循环找到数组中相加为target值的位置。

class Solution {
public:
    vector<int> twoSum(vector<int>& nums, int target) {
        for(int i=0;i<nums.size();i++)
        {
            for(int j=i+1;j<nums.size();j++)
            {
                if(nums[i]+nums[j]==target)
                {
                    return {i,j};
                }
            }
        }
        return {};
    }
};

方法二:利用map

class Solution {
public:
    vector<int> twoSum(vector<int>& nums, int target) {
        map<int,int> a;//提供一对一的hash
        vector<int> b(2,-1);//用来承载结果,初始化一个大小为2,值为-1的容器b
        for(int i=0;i<nums.size();i++)
        {
            //此为解题的关键。每次判断map中是否有key为target-nums[i],存在则证明找到i,j
            if(a.count(target-nums[i])>0)
            {
                b[0]=a[target-nums[i]];
                b[1]=i;
                break;
            }
            a[nums[i]]=i;//没找到则放入map中,num[i]为key值,i为value
        }
        return b;
    };
};

2. 两数相加

给你两个 非空 的链表,表示两个非负的整数。它们每位数字都是按照 逆序 的方式存储的,并且每个节点只能存储 一位 数字。

请你将两个数相加,并以相同形式返回一个表示和的链表。

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

示例 1:

输入:l1 = [2,4,3], l2 = [5,6,4]
输出:[7,0,8]
解释:342 + 465 = 807.
class Solution {
public:
    ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
        ListNode* Head=nullptr;//头指针,用于最后的返回
        ListNode* Temp=nullptr;//每次执行Temp=Temp->next用于添加后指针的数
        int flag=0;//进位标识
        while(l1||l2)
        {
            //null则置为0
            int l1_val=(l1?l1->val:0);
            int l2_val=(l2?l2->val:0);
            //第一次循环时,头指针与temp指针指向同一地址,也是首地址,这样最后返回时直接返回Head即可,在后面的操作对Temp进行
            if(!Head)
            {
                Head=Temp=new ListNode((l1_val+l2_val+flag)%10);
            }
            else
            {
                //赋下一位值,同时Temp=Temp->next
                Temp->next=new ListNode((l1_val+l2_val+flag)%10);
                Temp=Temp->next;
            }
            flag=(l1_val+l2_val+flag>=10?1:0);
            if(l1) l1=l1->next;
            if(l2) l2=l2->next;
        }
        //如果最后相加为10
        if(flag==1) Temp->next=new ListNode(1);
        return Head;
    }
};

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

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

示例 1:

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

方法一:滑动窗口

class Solution {
public:
    int lengthOfLongestSubstring(string s) {
        int start(0),end(0),length(0),length2(0);
        int s_length=s.length();
        //不断后移end,与前面已有字符进行比较
        while(end<s_length)
        {
            //与end前面所有的字符进行比较,如果存在 将start移向该index前面一个位置,下次end直接从该位置进行比较
            for(int index=start;index<end;index++)
            {
                if(s[index]==s[end])
                {
                    start=index+1;
                    //找到相同的,记录长度
                    length2=end-start+1;
                    break;
                }
            } 
            //未找到相同字符,记录长度
            length2=end-start+1;
            //除第一次外,和前一次进行比较取较大值
            length=max(length,length2);
            end++;
        }
        return length;
    }
};

方法二:利用set 思想类似

class Solution {
public:
    int lengthOfLongestSubstring(string s) {
        unordered_set<char>u;
        int left=0,right=0,Max=0;
        int n=s.size();
        while(right<n){
            //s[right]在set中不存在,插入该字符,并不断右移
            if(u.end()==u.find(s[right])){
                u.insert(s[right++]);
                Max=max(right-left,Max);
            } 
            //存在,将重复字符前面所有字符移除,left为重复字符下一位置
            else u.erase(s[left++]);
        }
        return Max;
    }
};

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

给定两个大小分别为 m 和 n 的正序(从小到大)数组 nums1 和 nums2。请你找出并返回这两个正序数组的 中位数 。

算法的时间复杂度应该为 O(log (m+n)) 。

示例 1:

输入:nums1 = [1,3], nums2 = [2]
输出:2.00000
解释:合并数组 = [1,2,3] ,中位数 

方法一(注意:该方法时间复杂度不满足!!!为O(m+n),仅仅自己记录):两个数组排序重新排序,第二个数组依次与前一个数组进行比较,如果小于,则插入,否则比较前面下一元素,直到到达小于元素。然后进行第二个数组的下一元素重复上一操作,但是是从上一元素插入位置开始比较。值得关心的有如果第一个数组先全部插入后,第二个还没插入完;以及第二个数组全部插入后,第一个还未插完。

class Solution {
public:
  double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
    int m = nums1.size();
    int n = nums2.size();
    vector<int> mergeV ;
    int last_place = 0;//记录第二个数组与第一个数组比较时第一个数组比的位置
    //第二个数组中元素进行比较
    for (int i = 0;i < n;i++)
    {
        //与第一个数组比较
        for (int j = last_place;j < m;j++)
        {
               //第二个数组小,则插入,记录位置,退出循环
              if (nums2[i] < nums1[j])
            {
                mergeV.push_back(nums2[i]);
                last_place = j;
                break;
            }
            //否则,插入第一个元素数据,记录比较的位置
            else
            {

                mergeV.push_back(nums1[j]);
                last_place = j+1;
            }
        }
        //第一个数组全部插完,第二个数组却还有剩余
        if (last_place == m)
        {
            mergeV.push_back(nums2[i]);
        }
    }
    //第二个数组全部插完,第一个数组却还有剩余
    if (mergeV.size() < n + m)
    {
        int a = m + n - mergeV.size();
        for (int i = 0;i < a;i++)
        {
            mergeV.push_back(nums1[last_place + i]);
        }
    }
        //返回中位数
        if(mergeV.size()%2==0)
        {
            //防止数据太大越界
            return    (mergeV[mergeV.size()/2]-mergeV[mergeV.size()/2-1])/2.0+mergeV[mergeV.size()/2-1];
        }
        else
        {return mergeV[(mergeV.size()-1)/2];} 

    }
};

方法二(待更新):

5. 最长回文子串

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

示例 1:

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

方法一:因为寻找最长回文子串,因此如果将该字符串反转,该子串一定也属于反转后的子串。因此,找到一最长子串同样是翻转后的字符串子串,同时该子串本身是回文即可。

class Solution {
public:
    string longestPalindrome(string s) {
        //存放结果字符串
        string res;
        //作为s的反转字符
        string rev=s;
        reverse(rev.begin(),rev.end());
        //temp,rev_temp都是在第二个for循环中使用
        string temp;
        string rev_temp;
        //初始长度赋为0
        int length=0;
        //本身就是   
        if(rev==s) return s;
        //第一层for循环用于从第一个依次向下寻找回文子串
        for(int i=0;i<s.length();i++)
        {       
            //第二层循环,用于与反转字符比较看是否是回文字符
            for(int j=i;j<s.length();j++)
            {
                if(length>=s.length()-i) break;//剩余字符比最大回文字符还短,明显可以退出循环
                //记录子串,不断扩大范围
                temp=temp+s[j];
                //temp不在rev中则不可能是回文,直接break
                if(rev.find(temp)!=-1)
                {
                    rev_temp=temp;
                    reverse(rev_temp.begin(),rev_temp.end());
                    //temp在rev中,且rev_temp与temp等,说明是回文子串
                    if(rev_temp==temp)
                    {
                        if(length>=temp.length())
                        {
                            continue;
                        }
                        else
                        {
                            //更新
                            res=temp;
                            length=temp.length();
                        }            
                    }
                }else break;
            }
            //temp重新赋空开始下一个位置字符比较
            temp="";
        }
        return res;
    }
};

方法二(动态规划):

方法三:(他人java版本,学习总结)

class Solution {
    public String longestPalindrome(String s) {
        if (s == null || s.length() == 0) {
            return "";
        }
//         保存起始位置,测试了用数组似乎能比全局变量稍快一点
        int[] range = new int[2];
        char[] str = s.toCharArray();
        for (int i = 0; i < s.length(); i++) {
//             把回文看成中间的部分全是同一字符,左右部分相对称
//             找到下一个与当前字符不同的字符
            i = findLongest(str, i, range);
        }
        return s.substring(range[0], range[1] + 1);
    }
    
    public static int findLongest(char[] str, int low, int[] range) {
//         查找中间部分
        int high = low;
        while (high < str.length - 1 && str[high + 1] == str[low]) {
            high++;
        }
//         定位中间部分的最后一个字符
        int ans = high;
//         从中间向左右扩散
        while (low > 0 && high < str.length - 1 && str[low - 1] == str[high + 1]) {
            low--;
            high++;
        }
//         记录最大长度
        if (high - low > range[1] - range[0]) {
            range[0] = low;
            range[1] = high;
        }
        return ans;
    }
}

6. Z 字形变换

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

比如输入字符串为 "PAYPALISHIRING" 行数为 3 时,排列如下:

P   A   H   N
A P L S I I G
Y   I   R

之后,你的输出需要从左往右逐行读取,产生出一个新的字符串,比如:"PAHNAPLSIIGYIR"

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

string convert(string s, int numRows);

示例 1:

输入:s = "PAYPALISHIRING", numRows = 3
输出:"PAHNAPLSIIGYIR"

思路:每一行通过一个字符串记录该行的字符,结果将每行字符相加即可。字符按Z型输入因此我们应当注意保存的方法,每下一个字符都是存到相邻的一行字符数组中(下一行或者上一行,取决于此时Z在上还是下),通过row的位置我们可以知道应该是上还是下,row值在0 - numrows-1范围,到达0或numrows-1时,flag变为相反值。如下代码:

class Solution {
public:
    string convert(string s, int numRows) {
        //保存每一行的字符,最后用result结合,一起输出
        string res[numRows],result;
        int size=s.length();
        //一行直接输出
        if(numRows==1) return s;
        int i=0;
        //flag用来控制row应该是加一还是减一;row值在0 - numrows-1,到达0或numrows-1时,flag变为相反值
        int row=0,flag=1;

        while(i<size)
        {
            //保存每一行的字符
            res[row]+=s[i];
            //row+1 or -1
            row+=flag;
            i++;
            if(row==numRows-1||row==0) flag=0-flag;
        }
        //结合输出
        for(i=0;i<numRows;i++)
        {
            result+=res[i];
        }
        return result;
    }
};

7. 整数反转

给你一个 32 位的有符号整数 x ,返回将 x 中的数字部分反转后的结果。

如果反转后整数超过 32 位的有符号整数的范围 [−2^31,2^31− 1] ,就返回 0。

假设环境不允许存储 64 位整数(有符号或无符号)。//不允许使用long型

示例 1:

输入:x = 123
输出:321

示例 2:

输入:x = -123
输出:-321

方法:如下注释。

class Solution {
public:
    int reverse(int x) {
        // y用来表示翻转后的数
        int y = 0; 
        while (x != 0) {
            // 溢出,那么输出为0;注意大于 小于的数字并不是32位可以表示的最大,最小,而是/10后的;
            if (y > 214748364 || y < -214748364) return 0;
            // 低位通过不断的*10变成高位
            y = y * 10 + x % 10; 
            //得到个位的数值
            x = x / 10;
        }
        return y; 
    }
};

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

请你来实现一个 myAtoi(string s) 函数,使其能将字符串转换成一个 32 位有符号整数(类似 C/C++ 中的 atoi 函数)。

函数 myAtoi(string s) 的算法如下:

  • 读入字符串并丢弃无用的前导空格
  • 检查下一个字符(假设还未到字符末尾)为正还是负号,读取该字符(如果有)。 确定最终结果是负数还是正数。 如果两者都不存在,则假定结果为正。
  • 读入下一个字符,直到到达下一个非数字字符或到达输入的结尾。字符串的其余部分将被忽略。
  • 将前面步骤读入的这些数字转换为整数(即,"123" -> 123, "0032" -> 32)。如果没有读入数字,则整数为 0 。必要时更改符号(从步骤 2 开始)。
  • 如果整数数超过 32 位有符号整数范围 [−231,  231 − 1] ,需要截断这个整数,使其保持在这个范围内。具体来说,小于 −231 的整数应该被固定为 −231 ,大于 231 − 1 的整数应该被固定为 231 − 1 。
  • 返回整数作为最终结果。

注意:

  • 本题中的空白字符只包括空格字符 ' ' 。
  • 除前导空格或数字后的其余字符串外,请勿忽略 任何其他字符。

示例 1:

输入:s = "42"
输出:42
解释:加粗的字符串为已经读入的字符,插入符号是当前读取的字符。
第 1 步:"42"(当前没有读入字符,因为没有前导空格)
         ^
第 2 步:"42"(当前没有读入字符,因为这里不存在 '-' 或者 '+')
         ^
第 3 步:"42"(读入 "42")
           ^
解析得到整数 42 。
由于 "42" 在范围 [-231, 231 - 1] 内,最终结果为 42 。

思路:先去空格,然后观察正负,统一转换为整数处理,当然不转换也可以方法也类似。计算值主要是通过ascll码,-‘0’。得到数值循环*10并加上余数即可,注意溢出问题。

class Solution {
public:
    int myAtoi(string s) {
        int i=0;int flag=1;
        int length=s.size();
        //去掉多余的空格
        for(i=0;i<length;i++)
        {
            if(s[i]==' ')  continue;
            else 
            {
                s=s.substr(i,length-i) ;
                break;
            }
        }
        length=s.length();
        //观察正负,并去除符号
        if(s[0]=='-') {s=s.substr(1,length-1);flag=-1;}
        else if(s[0]=='+') {s=s.substr(1,length-1);}
        //去除一次符号后,只要后面的连续的数字,其余皆可删除
        for(i=0;i<length;i++)
        {
            if(s[i]>='0'&&s[i]<='9')   continue;
            else break;
        }
        //得到纯数字字符串
        s=s.substr(0,i);
        int n=0;
        //字符串转换为int
        for(int j=0;j<i;j++)
        {
            //溢出判断
            if((n>INT_MAX/10 && flag==1)||(n==INT_MAX/10 && s[j]-'0'>7 && flag==1))   return INT_MAX;
            if((n>INT_MAX/10 && flag==-1)||(n==INT_MAX/10 && s[j]-'0'>=8 && flag==-1)) return INT_MIN;
            //一定要加(),不然先计算s[j]的ascll码可能溢出
            n=n*10+(s[j]-'0');
        }
        //正负
        return flag==1?n:-n;
    }
};

通过stringstream可以用较少的代码实现,可以ac

class Solution {
public:
    int myAtoi(string s) {
        stringstream ss;
        int n=0;
        ss<<s;
        ss>>n;
        return n;
    }
};

9. 回文数

给你一个整数 x ,如果 x 是一个回文整数,返回 true ;否则,返回 false 。

回文数是指正序(从左向右)和倒序(从右向左)读都是一样的整数。例如,121 是回文,而 123 不是。

示例 1:

输入:x = 121
输出:true

示例 2:

输入:x = -121
输出:false
解释:从左向右读, 为 -121 。 从右向左读, 为 121- 。因此它不是一个回文数。

思路:同样如代码所示,与第七题思路相似。

class Solution {
public:
    bool isPalindrome(int x) {
        if(x<0) return 0;
        //记录结果
        int y = 0; 
        int nub=x;
        while (x != 0) {
            // 溢出,那么输出为0;注意大于 小于的数字并不是32位可以表示的最大,最小,而是/10后的;其实并不很严谨,个位可以更精确
            if (y > 214748364 || y < -214748364) return 0;
            // 低位通过不断的*10变成高位
            y = y * 10 + x % 10; 
            //得到个位的数值
            x = x / 10;
        }
        if(y==nub) return 1;
        else return 0;
    }
};

10. 正则表达式匹配

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

  • '.' 匹配任意单个字符
  • '*' 匹配零个或多个前面的那一个元素

所谓匹配,是要涵盖 整个 字符串 s的,而不是部分字符串。

示例 1:

输入:s = "aa" p = "a"
输出:false
解释:"a" 无法匹配 "aa" 整个字符串。

待补充。

11. 盛最多水的容器

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

说明:你不能倾斜容器。

示例 1:

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

思路分析:双指针, 分别用left,right指向容器的两端,计算第一次的容量,比较二者之间高度较小的一端,向另一端移动,原因如下:假设移动高的一端可以找到更大的容量,此时二者的底宽度在减小,而这个容量肯定比原来容量小,因为 容量=底*高,底在变小,第二次的可能的最大容量也是同第一次的高相同,所以容量在变小。因此应当移动较低的一端。一直到最后left,right相邻,此时最大capacity可以确定。

class Solution {
public:
    int maxArea(vector<int>& height) {
        //left,right分别指向左右两端,capacity记录最大容量
        int left=0,right=height.size()-1,capacity=0;
        while(left<right)
        {
            //三目运算写法
            capacity= height[left]<height[right]?max(capacity,(right-left)*height[left++]):max(capacity,(right-left)*height[right--]);
        }
        return capacity;
    }
};

12. 整数转罗马数字

罗马数字包含以下七种字符: I, V, X, LCD 和 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:

输入: num = 3
输出: "III"

示例 2:

输入: num = 4
输出: "IV"

思路:贪心哈希表。从最大的1000比较,直到1。用一个string result存放结果,如果大于,则加入相应的字符。

class Solution {
public:
    string intToRoman(int num) {
        //增添了几个特殊
        int number[]={1000,900,500,400,100,90,50,40,10,9,5,4,1};
        //与number一一对应
        string res[]={"M","CM","D","CD","C","XC","L","XL","X","IX","V","IV","I"};
        string result;
        for(int i=0;i<13;i++)
        {
            while(num>=number[i])
            {
                num-=number[i];
                result+=res[i];
            }
        }

        return result;
    }
};

13. 罗马数字转整数

罗马数字包含以下七种字符: I, V, X, LCD 和 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

思路:与上一题相似。贪心希哈表

class Solution {
public:
    int romanToInt(string s) {
        int number[]={1000,900,500,400,100,90,50,40,10,9,5,4,1};
        string res[]={"M","CM","D","CD","C","XC","L","XL","X","IX","V","IV","I"};
        int result=0;int flag=0;
        for(int i=0;i<s.length();i++)
        {
            //用一个flag可以减少不必要的比较,因为单位从大到小
            for(int j=flag;j<13;j++)
            { 
                //有一个字符单位,两个字符单位
                if(s.substr(i,1)==res[j])
                {
                    result+=number[j];
                    flag=j;
                }else if(s.substr(i,2)==res[j])
                {
                    result+=number[j];
                    ++i;
                    flag=j;
                }
            }
        }

        return result;
    }
};

方法二:也比较容易理解。没有了多余的key值,遇到从左到右遇到小的单位将结果减去该key对应值,大的加上即可。

class Solution {
public:
    int romanToInt(string s) {
        int result=0;
        map<char,int> luomab={
            {'I',1},
            {'V',5},
            {'X',10},
            {'L',50},
            {'C',100},
            {'D', 500},
            {'M', 1000}
        };//初始化哈希表
        for(int i=0;i<s.length();i++)
        {
            if(luomab[s[i]] < luomab[s[i+1]])
                result -= luomab[s[i]];
            else
            {
                result += luomab[s[i]];
            }
        }
        return result;
    }
};

14. 最长公共前缀

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

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

示例 1:

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

思路:将strs数组内部的字符串重新排序,因为求的是最大前缀,只要比较第一个与最后一个相同的前缀即可。

class Solution {
public:
    string longestCommonPrefix(vector<string>& strs) {
        //将strs数组内部的字符串重新排序,因为求的是最大前缀,只要比较第一个与最后一个相同的前缀即可。
        sort(strs.begin(),strs.end());
        string firstStr=strs[0];
        string endStr=strs[strs.size()-1];
        int i;//用来记录前缀长度
        for(i=0;i<firstStr.size()&&firstStr[i]==endStr[i];i++);
        string res=firstStr.substr(0,i);
        return res;
    }
};

15. 三数之和

给你一个包含 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 = []
输出:[]

思路:三数相加为0,可以遍历整个数组,找到另外两个数相加为该数的相反数即可。注意:答案中不可包含重复的三元组,因此我们应当仔细考虑  去重  的问题。首先将整个数组排序,有序的数组将可以使我们的操作变得更加方便。判断第一个与最后一个数组与0大小关系即可初步判断是否有可能有答案。然后逐个遍历数组中的大小不同元素(原因后面解释),通过双指针一个指向该元素的下一元素,一个指向末尾的原数,不断寻找三数相加为0的位置(与前面11题最大容量类似)。三数想加大于0移动右指针,小于0移动左指针。寻找到等于零则记录,添加到数组之中,同时继续移动指针寻找其他可能解,并且在移动指针寻找解之前将左右指针移动到与此时值不同的位置。例如【-2,0,0,2,2】在对第一个元素-2求后面和为2时,第二个元素+第五个元素 与 第三个元素+第四个元素 均为答案,但是应该只输出一组。所以移动指针到不同的值后,开始继续寻找解直到start与end相邻,这便是前面说的逐个遍历数组中的大小不同元素的原因,因为前面已经找到了中间所有的值,所以对相邻的两个值相同的元素如果有解,在前面一个元素寻找解时已经找到,故可以直接跳过。

class Solution {
public:
    vector<vector<int>> threeSum(vector<int>& nums) {
        sort(nums.begin(),nums.end());
        int length=nums.size();
        vector<vector<int>> res;
        int start,end;            
        for(int i=0;i<length;i++)
        {
            //最小大于0,最大小于0,显然不可能
            if(nums[0]>0||nums[length-1]<0) return res; 
            //去重数组相邻值相等
            if(i>=1&&nums[i]==nums[i-1]) continue;
            start=i+1;end=length-1;
            while(start<end)
            {
                if(nums[i]+nums[start]+nums[end]>0)
                {
                    end--;
                } 
                else if(nums[i]+nums[start]+nums[end]<0)
                {
                    start++;
                }else
                {
                    res.push_back(vector<int>{nums[i],nums[start],nums[end]});
                    //下面两行为了去重
                    while(start<end&&nums[start]==nums[start+1]){start++;}
                    while(start<end&&nums[end]==nums[end-1]){end--;}
                    //找到一组答案start,end均收缩。如果start>=end则会退出循环了
                    start++;end--;
                }
            }
        }
        return res;
    }
};

16. 最接近的三数之和

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

返回这三个数的和。

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

示例 1:

输入:nums = [-1,2,1,-4], target = 1
输出:2
解释:与 target 最接近的和是 2 (-1 + 2 + 1 = 2) 。

思路:与上题类似,同样用到了双指针。上题可以看成目标为0,注意这一题去重与上题有所不同。

class Solution {
public:
    int threeSumClosest(vector<int>& nums, int target) {
        //先排序
        sort(nums.begin(),nums.end());
        //双指针
        int start,end;
        //closestNum为最接近值,初始设为前三数之和,absolute为差值绝对值
        int closestNum=nums[0]+nums[1]+nums[2],absolute=closestNum-target>0?closestNum-target:target-closestNum;
        //与上一题思路一致
        for(int i=0;i<nums.size();i++)
        {
            start=i+1;
            end=nums.size()-1;
            if(i>0&&nums[i]==nums[i-1]) continue;
            while(start<end)
            {
                if(nums[i]+nums[start]+nums[end]>target) 
                {
                    closestNum=(nums[i]+nums[start]+nums[end]-target)<absolute?nums[i]+nums[start]+nums[end]:closestNum;
                    absolute=min(nums[i]+nums[start]+nums[end]-target,absolute);
                    end--;
                }else if(nums[i]+nums[start]+nums[end]<target)
                {
                    closestNum=(target-(nums[i]+nums[start]+nums[end]))<absolute?nums[i]+nums[start]+nums[end]:closestNum;
                    absolute=min(target-(nums[i]+nums[start]+nums[end]),absolute);
                    start++;
                }else if(nums[i]+nums[start]+nums[end]==target)
                {
                    return target;
                }
            }
        }
        return closestNum;      
    }
};

17. 电话号码的字母组合

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

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

示例 1:

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

思路:递归回溯。最重要的时回溯函数(backtracking),通过判断index与字符串长度是否相同可以确认终止条件,return。for循环之中,可以遍历该数字对应的字符全部情况,配合递归,可以得到与下一数字对应字符组合的全部情况。(关于回溯可查看:关于回溯算法,你该了解这些!

class Solution {
public:
    string s;//全局字符串,用来记录组合成的字符,当达到与字符串长度相同时,加入vector<string>
    unordered_map<char,string> map{
            {'2', "abc"},
            {'3', "def"},
            {'4', "ghi"},
            {'5', "jkl"},
            {'6', "mno"},
            {'7', "pqrs"},
            {'8', "tuv"},
            {'9', "wxyz"}
        };
    vector<string> letterCombinations(string digits) {
        //为空,直接返回空
        if(digits=="") return {};
        //记录组合
        vector<string> res;
        backtracking(digits,0,res);
        return res;
    }
    
    //回溯函数
    void backtracking(string digit,int index,vector<string> &res)
    {         
        //当index=字符串长度时,说明string s中的字符串长度与digits长度一样,因此向res中push
        //这也是回溯函数的终止条件,因此return
        if(index==digit.size())
        {
            res.push_back(s);
            return;
        }
        //映射,得到当时字符的可能的字符
        char num=digit[index];
        string temp=map[num];
        //for循环与递归组合,这样可以得到所有组合的情况
        for(int i=0;i<temp.size();i++)
        {
            s.push_back(temp[i]);//操作
            backtracking(digit,index+1,res);//递归
            s.pop_back();//回溯操作
        }
    }
};

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

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

示例 1:

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

方法一:暴力法。先遍历一遍链表找出整个链表的长度,再删除对应位置即可。注意特殊情况。

/**
 * 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) {
        int length=0;
        ListNode* temp=head;
        while(temp)
        {
            temp=temp->next;
            length++;
        }
        if(length==1) 
        {
            head=nullptr;
            return head;
        }
        if(length==n)
        {
            head=head->next;
            return head;
        }
        temp=head;
        for(int i=1;i<=length-n;i++)
        {   
            if(i==length-n)
            {
                ListNode* tempPtr=temp->next;
                temp->next=tempPtr->next;
                delete tempPtr;
            }
            temp=temp->next;
        }
        return head;

    }
};

方法二:采用前后双指针。具体思想为:设置一个头指针,指向第一个元素(有利于操作删除第一个元素)。设置一个前指针,后指针两个指针初始都在头指针处。前指针在前,而后指针在后,后指针先向后走n个位置,然后两个指针同时向后,这样,当后指针的next为null时,前指针的next即为欲删除元素。此时进行删除即可。注意删除元素为head时,不可直接delete该内存地址,否则return时会出现问题。具体查看代码

/**
 * 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) {
        //前后指针
        ListNode* front,*behind;
        //一个头节点,指向第一个节点。主要是为了方便删除head节点
        ListNode* virtualHead=new ListNode();
        virtualHead->next=head;
        //初始
        front=virtualHead;
        behind=virtualHead;
        //behind在front前n步
        while(n!=0)
        {
            behind=behind->next;
            n--;
        }
        //移动front和behind,当behind到达最后一个节点时,front到达欲删除节点前一位置
        while(behind->next)
        {
            front=front->next;
            behind=behind->next;
        }
        //删除操作,同时删除该内存空间
        ListNode* temp=front->next;
        front->next=temp->next;
        //如果temp是head则重新赋值head,否则删除temp
        if(temp==head) head=front->next;
        else delete temp;
        delete virtualHead;
        return head;
    }
};

20. 有效的括号

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

有效字符串需满足:

  1. 左括号必须用相同类型的右括号闭合。
  2. 左括号必须以正确的顺序闭合。

示例 1:

输入:s = "()"
输出:true

思路:根据题意,左右括号必须匹配,因此可以知道:第一个左括号对应最后一个右括号,最后一个左括号最先匹配右括号,所以我们可以通过 来实现,每遇到一个左括号,就压入一个对应的右括号,在遇到右括号时,与栈顶括号对比,相同证明符号条件,并弹出。在最后,栈中元素全部对比完,也就是栈为空时,证明该字符串符号题意。

class Solution {
public:
    bool isValid(string s) {
        stack<int> stack;
        int length=s.size();
        if(length%2==1) return 0;
        for(int i=0;i<length;i++)
        {
            if(s[i]=='(') stack.push(')');
            else if(s[i]=='[') stack.push(']');
            else if(s[i]=='{') stack.push('}');
            else{
                if(stack.empty()) return 0;
                if(s[i]==stack.top())
                {
                    stack.pop();
                }
                else return 0;
            }
        }
        if(stack.empty()) return 1;
        return 0;
    }
};

21. 合并两个有序链表

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

示例 1:

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

思路:首先新建一个头节点,这样有利于对首元节点的操作。对比两个链表第一个元素大小,将头节点指向 值较小那个元素,同时更新头节点为该元素,并从list中删除该节点。在两个链表都非空时反复执行上一步,直到一链表为空,直接将另一链表连在后面。

class Solution {
public:
    ListNode* mergeTwoLists(ListNode* list1, ListNode* list2) {
        ListNode* preHead=new ListNode();
        ListNode* pre=preHead;
        //其中有链表为空
        if(!list1) return list2;
        if(!list2) return list1;
        //pre始终指向list1,list2值较小的那个,同时更新pre为指向的那个元素,相应的list将刚刚那个元素脱落。
        while(list1&&list2)
        {
            if(list1->val<=list2->val)
            {
                pre->next=list1;
                list1=list1->next;
            }else
            {
                pre->next=list2;
                list2=list2->next;
            }
            pre=pre->next;
        }
        //最后如果一个链表已经为空,直接将另一链表连在后面即可。
        pre->next= list1?list1:list2;
        return preHead->next;
    }
};

22. 括号生成

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

示例 1:

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

思路:采用递归回溯。前面有一题为回溯,这题条件不同其余大都类似。

class Solution {
public:

    void backtracking(string cur,vector<string>&res,int left,int right,int n)
    {
        if(cur.size()==2*n)
        {
            res.push_back(cur);
            return;
        }
        if(left<n)
        {
            cur.push_back('(');
            backtracking(cur,res,left+1,right,n);
            cur.pop_back();
        }
        if(right<left)
        {
            cur.push_back(')');
            backtracking(cur,res,left,right+1,n);
            cur.pop_back();
        }
        return;
    }

    vector<string> generateParenthesis(int n) {
        string cur;
        vector<string> result;
        backtracking(cur,result,0,0,n);
        return result;
    }
};

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

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

示例 1:

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

思路:迭代法。同样设置一个虚拟头节点,方便对首元节点操作。当链表至少有两个节点时可以进行交互。具体查看代码,还是比较清晰的。

class Solution {
public:
    ListNode* swapPairs(ListNode* head) {
        ListNode *preHead=new ListNode();
        preHead->next=head;
        ListNode *Head=preHead;
        while(Head)
        {
            if(Head->next&&Head->next->next)
            {
                ListNode* temp=Head->next;
                Head->next=temp->next;
                temp->next=temp->next->next;
                Head->next->next=temp;
                Head=Head->next->next;
            }else break;          
        }
        return preHead->next;
    }
};

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

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

由于在某些语言中不能改变数组的长度,所以必须将结果放在数组nums的第一部分。更规范地说,如果在删除重复项之后有 k 个元素,那么 nums 的前 k 个元素应该保存最终结果。

将最终结果插入 nums 的前 k 个位置后返回 k 。

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

思路:如代码所示。因为题目上有说不需要考虑后面元素因此我们只需要前面的元素不重复且有序即可。

class Solution {
public:
    int removeDuplicates(vector<int>& nums) {
	    if (nums.size() < 2) return nums.size();
	    int j = 0;
        // 题目有解释不需要考虑后面元素
	    for (int i = 1; i < nums.size(); i++)
		    if (nums[j] != nums[i]) nums[++j] = nums[i];    
	    return ++j;
}
};

27. 移除元素

给你一个数组 nums 和一个值 val,你需要 原地 移除所有数值等于 val 的元素,并返回移除后数组的新长度。

不要使用额外的数组空间,你必须仅使用 O(1) 额外空间并 原地 修改输入数组

元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素。

思路:与上题类似。

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

28. 实现 strStr()

实现 strStr() 函数。

给你两个字符串 haystack 和 needle ,请你在 haystack 字符串中找出 needle 字符串出现的第一个位置(下标从 0 开始)。如果不存在,则返回  -1 

说明:

当 needle 是空字符串时,我们应当返回什么值呢?这是一个在面试中很好的问题。

对于本题而言,当 needle 是空字符串时我们应当返回 0 。这与 C 语言的 strstr() 以及 Java 的 indexOf() 定义相符。

思路:就遍历整个数组,与给的子串对比是否相等。

当然可以用跟高级的算法,例如KMP。

class Solution {
public:
    int strStr(string haystack, string needle) {
        //return haystack.find(needle);
        if(needle=="") return 0;
        if(needle.size()>haystack.size()) return -1;
        int haystack_length=haystack.size();
        int needle_length=needle.size();
        int index;

        for(int i=0;i<haystack_length-needle_length+1;++i)
        {
            if(haystack[i]==needle[0])
            {
                int j=0;
                index=i;
                while(j!=needle_length)
                {
                    if(haystack[i]==needle[j])
                    {                   
                        if(j==needle_length-1) return index;
                        i++;
                        j++;
                    }else break;
                }
                i=index;
            }
        }
        return -1;
    }
};

29. 两数相除

给定两个整数,被除数 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

思路:首先进行特殊值处理。然后将除数以及被除数变成负数(因为int在取值范围中,INT_MIN 绝对值大于 INT_MAX)。显然通过简单的循环相减可以得到答案,但是该题会超时。因此我们采用 倍增优化。举个例子:-8/-3:(-3+(-3))>-8因此,可以将除数翻倍,同时用一个数值从1同样翻倍,作为商,此时除数-6,商2;-6+(-6)<-8因此退出循环,同时执行return 商(2)+div(-2,-2),此时计算div(-2,-2)可以得到return 商(1)+div(0,-2),div(0,-2)为0,因此结果就是2+1+0=3;感觉有点抽象,看代码应该可以更好的理解。

class Solution {
public:
    int divide(int dividend, int divisor) {
        //特殊情况
        if(dividend==0||(dividend<divisor&&dividend>0)||(dividend<0)&&dividend>divisor) return 0;
        if(dividend==INT_MIN&&divisor==-1) return INT_MAX;
        if(dividend==INT_MIN&&divisor==1) return INT_MIN;
        //商正负标志
        int flag=0;
        if((dividend>0&&divisor<0)||(dividend<0&&divisor>0)) flag=-1;
        //因为int在取值范围中,INT_MIN 绝对值大于 INT_MAX。因此将他们都转换为负数处理,最后在转换回来。
        dividend= dividend<0?dividend:-dividend;
        divisor = divisor<0?divisor:-divisor;
        int result=div(dividend,divisor);
        return flag==0?result:-result;
    }

    int div(int dividend,int divisor)
    {
        //递归的终止条件
        if(dividend>divisor) return 0;
        int count=1;//记录商
        int temp=divisor;
        //如果不满足该条件第一次相加就会溢出
        if(divisor>-1073741824)
        {
            while(divisor+divisor>dividend)
            {
                //除数翻倍
                divisor+=divisor;
                count+=count;
                //防止溢出
                if(divisor<-1073741824) break;
            }
        }
        //被除数在不断变大,直到(dividend>divisor)
        return count+div(dividend-divisor,temp);
    }
};

31. 下一个排列

整数数组的一个 排列  就是将其所有成员以序列或线性顺序排列。

  • 例如,arr = [1,2,3] ,以下这些都可以视作 arr 的排列:[1,2,3][1,3,2][3,1,2][2,3,1] 。

整数数组的 下一个排列 是指其整数的下一个字典序更大的排列。更正式地,如果数组的所有排列根据其字典顺序从小到大排列在一个容器中,那么数组的 下一个排列 就是在这个有序容器中排在它后面的那个排列。如果不存在下一个更大的排列,那么这个数组必须重排为字典序最小的排列(即,其元素按升序排列)。

  • 例如,arr = [1,2,3] 的下一个排列是 [1,3,2] 。
  • 类似地,arr = [2,3,1] 的下一个排列是 [3,1,2] 。
  • 而 arr = [3,2,1] 的下一个排列是 [1,2,3] ,因为 [3,2,1] 不存在一个字典序更大的排列。

给你一个整数数组 nums ,找出 nums 的下一个排列。

必须 原地 修改,只允许使用额外常数空间。

示例 1:

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

思路:主要是理解三个数排列,怎么找到最小大于该数的规律即可。假如该数有n位,寻找恰好比该数大的方式:从(左到右)n-1位开始与后面从最右边开始到该位比较,如果找到比之大的数值则可以交换位置,找到大于数值,否则n-1向左移动一位,第n-2位与右边从右向左开始比较,找到比该位大的则交换位置,结束否则n-2向左移动一位......注意:交换后还需要将该位后面从小到大排序,这样才是大于该数的最小的值。特殊情况为该数为最大值,则我们倒向输出就可以得到最小值。

class Solution {
public:
    void nextPermutation(vector<int>& nums) {
        int length=nums.size();
        int flag=0;
        for(int i=length-2;i>=0;i--)
        {
            for(int j=length-1;j>i;j--)
            {
                if(nums[i]<nums[j])
                {
                    //交换找到的值
                    swap(nums[i],nums[j]);
                    //交换后还需要将后面从小到大排序,这样才是最小的
                    sort(nums.begin()+i+1,nums.end());
                    flag=1;
                    break;
                }
            }
            if(flag==1) break;
        }
        //最大值情况
        if(flag==0)
        {
            int i=0;int j=length-1;
            while(j>i)
            {
                swap(nums[i++],nums[j--]);
            }
        }
    }
};

33. 搜索旋转排序数组

整数数组 nums 按升序排列,数组中的值 互不相同 。

在传递给函数之前,nums 在预先未知的某个下标 k0 <= k < nums.length)上进行了 旋转,使数组变为 [nums[k], nums[k+1], ..., nums[n-1], nums[0], nums[1], ..., nums[k-1]](下标 从 0 开始 计数)。例如, [0,1,2,4,5,6,7] 在下标 3 处经旋转后可能变为 [4,5,6,7,0,1,2] 。

给你 旋转后 的数组 nums 和一个整数 target ,如果 nums 中存在这个目标值 target ,则返回它的下标,否则返回 -1 。

示例 1:

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

进阶:你可以设计一个时间复杂度为 O(log n) 的解决方案吗?

思路:题目要求时间复杂度,因此可以想到二分法。该题与普通二分法不同地方在于,部分有序。我们可以知道这个部分有序肯定是前半部分,或者后半部分。所以,我们总是可以利用有序的那一部分判断我们找的值在哪一部分

class Solution {
public:
    int search(vector<int>& nums, int target) {
        int length=nums.size();
        int left=0;
        int right=length-1;
        //特殊情况
        if(length==0) return -1;
        if(length==1) return target==nums[0]?0:-1;
        //一定有一部分是有序的,通过这个部分来判断应该缩小范围在那个部分。注意是<=
        while(left<=right)
        {
            int mid=(left+right)/2;
            if(nums[mid]==target) return mid;
            //前面是有序
            if(nums[0]<=nums[mid])
            {
                if(target<nums[mid]&&target>=nums[left]) right=mid-1;
                else left=mid+1;
            }else{
                //后面有序
                if(target>nums[mid]&&target<=nums[right]) left=mid+1;
                else right=mid-1;
            }
        }
        return -1;
    }
};

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

难度中等1455

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

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

  • 你可以设计并实现时间复杂度为 O(log n) 的算法解决此问题吗?

示例 1:

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

思路:利用二分思想先找其左边界,再找其右边界即可,找左边界的时候,由右侧逼近;找右边界的时候,由左侧逼近。注意循环条件,容易是否会造除死循环。

class Solution {
public:
    vector<int> searchRange(vector<int>& nums, int target) {
        int length=nums.size();
        vector<int> res(2,-1);
        //特殊情况
        if(nums.empty()) return res;
        if(length==1&&nums[0]!=target) return res;
        if(length==1&&nums[0]==target) return {0,0};
        int left=0,right=length-1,mid;
        //寻找左边界,从右侧逼近
        while(left<right)
        {
            //防止溢出
            mid=left+(right-left)/2;
            if(nums[mid]>=target) right=mid;
            else left=mid+1;
        }
        if(nums[left]==target) res[0]=left;
        else return res;
        right=length-1; 
        //寻找右边界,从左侧逼近
        while(left<right)
        {
            mid=left+(right-left)/2;
            //如果不加一的话会造成死循环
            if(nums[mid]<=target) left=mid+1;
            else right=mid-1;
        }
        //也可以在while前赋值right为length,就可以不判断,比较难以想到
        if(nums[left]==target) res[1]=left;
        else res[1]=left-1;
        return res;
    }
};

35. 搜索插入位置

给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。

请必须使用时间复杂度为 O(log n) 的算法。

示例 1:

输入: nums = [1,3,5,6], target = 5
输出: 2

思路:普通二分法思想。

class Solution {
public:
    int searchInsert(vector<int>& nums, int target) {
        if(nums.empty()) return 0;
        int left=0,right=nums.size()-1,mid;
        if(target>nums[right]) return right+1;
        while(left<right)
        {
            mid=left+(right-left)/2;
            if(nums[mid]>=target) right=mid;
            else left=mid+1;
        }
        return right;
    }
};

36. 有效的数独

请你判断一个 9 x 9 的数独是否有效。只需要 根据以下规则 ,验证已经填入的数字是否有效即可。

  1. 数字 1-9 在每一行只能出现一次。
  2. 数字 1-9 在每一列只能出现一次。
  3. 数字 1-9 在每一个以粗实线分隔的 3x3 宫内只能出现一次。(请参考示例图)

注意:

  • 一个有效的数独(部分已被填充)不一定是可解的。
  • 只需要根据以上规则,验证已经填入的数字是否有效即可。
  • 空白格用 '.' 表示。

示例 1:

思路:主要目的是三个区域均无重复数字则表示有效数独,因此我们可以使用三个9*9的二维数组,初始每个位置均为0,当某一位置有数字m时,在该行的第m个元素记录变更为1,这样下次如果该行再次出现数字m,我们就可以判断这不是一个有效的数独。同理,我们可以对列进行类似的操作,注意,该数字在行和列的数组中,储存的坐标是不一样的,以示例一的左少角的5为例,在行数组中,存到row[0][4],而在列数组中,存到line[4][0]。3*3数组思路类似,有个很巧妙的思路计算数独中元素位于第几个3*3数组,j/3+(i/3)*3。具体实现查看代码。

class Solution {
public:
    bool isValidSudoku(vector<vector<char>>& board) {
        //记录每一行的
        int row[9][9]={0};
        //记录每一列的
        int line[9][9]={0};
        //记录3*3的
        int box[9][9]={0};
        //只用遍历一次,即可判断是否为有效数独
        for(int i=0;i<9;i++)
        {
            for(int j=0;j<9;j++)
            {
                if(board[i][j]=='.') continue;
                int num=board[i][j]-'0';
                if(row[i][num-1]) return false;
                if(line[num-1][j]) return false;
                if(box[j/3+(i/3)*3][num-1]) return false;
                row[i][num-1]=1;
                line[num-1][j]=1;
                box[j/3+(i/3)*3][num-1]=1;
            }
        }
        return true;
    }
};

 

39. 组合总和

给你一个 无重复元素 的整数数组 candidates 和一个目标整数 target ,找出 candidates 中可以使数字和为目标数 target 的 所有 不同组合 ,并以列表形式返回。你可以按 任意顺序 返回这些组合。

candidates 中的 同一个 数字可以 无限制重复被选取 。如果至少一个数字的被选数量不同,则两种组合是不同的。 

对于给定的输入,保证和为 target 的不同组合数少于 150 个。

示例 1:

输入:candidates = [2,3,6,7], target = 7
输出:[[2,2,3],[7]]
解释:
2 和 3 可以形成一组候选,2 + 2 + 3 = 7 。注意 2 可以使用多次。
7 也是一个候选, 7 = 7 。
仅有这两种组合。

思路:组合,可以想到使用回溯是可以实现的。

class Solution {
public:
    vector<vector<int>> res;
    vector<int> path;
    vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
        backtracking(candidates,target,0,0);
        return res;
    }
    void backtracking(vector<int> &candidates,int target,int sum,int index)
    {
        if(sum==target) 
        {
            res.push_back(path);
            return;
        }
        if(sum>target) return;
        for(int i=index;i<candidates.size();i++)
        {
            sum+=candidates[i];
            path.push_back(candidates[i]);
            backtracking(candidates,target,sum,i);
            sum-=candidates[i];
            path.pop_back();
        }
    }
};

217. 存在重复元素

给你一个整数数组 nums 。如果任一值在数组中出现 至少两次 ,返回 true ;如果数组中每个元素互不相同,返回 false 。

示例 1:

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

思路1.暴力for循环就不说了,但是会导致超时。

思路2.采用哈希map,数组中的数字对应一值,如发现后面的已经存在直接返回false,也很好实现。

思路3.排序后对比前后元素是否等。在此处就贴出该代码。

class Solution {
public:
    bool containsDuplicate(vector<int>& nums) {
        int n=nums.size();
        sort(nums.begin(),nums.end());
        int i=0;
        while(i!=n-1)
        {
            if(nums[i]==nums[i+1]) return 1;
            i++;
        }
        return 0;
    }
};

53. 最大子数组和

给你一个整数数组 nums ,请你找出一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。

子数组 是数组中的一个连续部分。

示例 1:

输入:nums = [-2,1,-3,4,-1,2,1,-5,4]
输出:6
解释:连续子数组 [4,-1,2,1] 的和最大,为 6 。

思路:贪心思想。具体想法为:当前面一个数与后面一个数相加大于后面数时,就将后面数更新为他们之和(可以这样想,如果前面为负数,那么肯定和小于不更新;如果前面是整数,肯定更新;后面如果是负数,如果前面的正数相加后任然为负数,我们理应抛弃,但是我们任然更新为这个较大的负数,因为在后面遇到正数时我们自然同样会抛弃并不影响结果;如果后面是正数,前面正数时更新),Max一直更新为当前数组位置与Max中更大的那个数。

class Solution {
public:
    int maxSubArray(vector<int>& nums) {
        int n=nums.size(),i=0;
        int Max=nums[0];
        while(i!=n-1)
        {
            //这一行也可以是   if(nums[i]+nums[i+1]>0&&nums[i]>0) nums[i+1]=nums[i]+nums[i+1];更好理解
            nums[i+1]= nums[i]+nums[i+1]>nums[i+1]?nums[i]+nums[i+1]:nums[i+1];                  
            Max=max(Max,nums[i+1]);   
            i++;
        }
        return Max;
    }
};

88. 合并两个有序数组

给你两个按 非递减顺序 排列的整数数组 nums1 和 nums2,另有两个整数 m 和 n ,分别表示 nums1 和 nums2 中的元素数目。

请你 合并 nums2 到 nums1 中,使合并后的数组同样按 非递减顺序 排列。

注意:最终,合并后数组不应由函数返回,而是存储在数组 nums1 中。为了应对这种情况,nums1 的初始长度为 m + n,其中前 m 个元素表示应合并的元素,后 n 个元素为 0 ,应忽略。nums2 的长度为 n 。

提示:

nums1.length == m + n
nums2.length == n
0 <= m, n <= 200
1 <= m + n <= 200
-109 <= nums1[i], nums2[j] <= 109
示例 1:

输入:nums1 = [1,2,3,0,0,0], m = 3, nums2 = [2,5,6], n = 3
输出:[1,2,2,3,5,6]
解释:需要合并 [1,2,3] 和 [2,5,6] 。
合并结果是 [1,2,2,3,5,6] ,其中斜体加粗标注的为 nums1 中的元素。

思路:因为num1的空间其实后面已经预留了位置,因此我们并不需要开辟新的数组。直接从后面比较数组中较大的那个元素,将其放入num1的后端,然后移动该数组的指针指向前一个元素。重复执行。如果num2中还有剩余元素,num1指针已经移动到0之前,则直接将那些元素复制到num1即可。

class Solution {
public:
    void merge(vector<int>& nums1, int n, vector<int>& nums2, int m) {
        if(m==0) return;
        if(n==0) 
        {
            nums1=nums2;
            return;
        }
        int after=m+n-1;
        m--;
        n--;
        while(m>=0&&n>=0)
        {
            if(nums2[m]>=nums1[n])     nums1[after--]=nums2[m--];
            else   nums1[after--]=nums1[n--];
        }
        while(m>=0)    nums1[m--]=nums2[m];     
        return;
    }
};

350. 两个数组的交集 II

给你两个整数数组 nums1 和 nums2 ,请你以数组形式返回两数组的交集。返回结果中每个元素出现的次数,应与元素在两个数组中都出现的次数一致(如果出现次数不一致,则考虑取较小值)。可以不考虑输出结果的顺序。

示例 1:

输入:nums1 = [1,2,2,1], nums2 = [2,2]
输出:[2,2]

思路:利用哈希表,key值为数组中的元素对应的值为出现的次数。

class Solution {
public:
    vector<int> intersect(vector<int>& nums1, vector<int>& nums2) {
        if(nums2.empty()||nums1.empty())     return {};
        unordered_map<int,int> map;
        vector<int> res;
        for(int i=0;i<nums1.size();i++)
        {
            map[nums1[i]]++;
        }
        for(int i=0;i<nums2.size();i++)
        {
            if(map[nums2[i]])
            {
                if(map[nums2[i]]!=0)
                {
                    res.push_back(nums2[i]);
                    map[nums2[i]]--;
                }  
            } 
        }
        return res;
    }
};

121. 买卖股票的最佳时机

给定一个数组 prices ,它的第 i 个元素 prices[i] 表示一支给定股票第 i 天的价格。

你只能选择 某一天 买入这只股票,并选择在 未来的某一个不同的日子 卖出该股票。设计一个算法来计算你所能获取的最大利润。

返回你可以从这笔交易中获取的最大利润。如果你不能获取任何利润,返回 0 。

示例 1:

输入:[7,1,5,3,6,4]
输出:5
解释:在第 2 天(股票价格 = 1)的时候买入,在第 5 天(股票价格 = 6)的时候卖出,最大利润 = 6-1 = 5 。
     注意利润不能是 7-1 = 6, 因为卖出价格需要大于买入价格;同时,你不能在买入前卖出股票。

思路:同样是贪心。与前面连续最大和有类似之处。

class Solution {
public:
    int maxProfit(vector<int>& prices) {
        int Min=prices[0];
        int benift=0;
        for(int i=0;i<prices.size();i++)
        {
            Min=min(Min,prices[i]);
            benift=max(benift,prices[i]-Min);
        }
        return benift;
    }
};

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值