Leetcode_入门_双指针

Leetcode_入门_双指针

  • 1、两数之和(167、Easy)
    • 1)题目要求
    • 2)我的解法
    • 3)其他解法
    • 4)自己的优化代码
    • 5)学到的东西
  • 2、平方数之和(633、Easy)
    • 1)题目要求
    • 2)我的解法
    • 3)其他解法
    • 4)自己的优化代码
    • 5)学到的东西
  • 3、反转字符串中的元音字符(345、Easy)
    • 1)题目要求
    • 2)我的解法
    • 3)其他解法
    • 4)自己的优化代码
    • 5)学到的东西
  • 4、 最长子序列(524、Medium)
    • 1)题目要求
    • 2)我的解法
    • 3)其他解法
    • 4)自己的优化代码
    • 5)学到的东西
  • 5、回文字符串(680、Easy)
    • 1)题目要求
    • 2)我的解法
    • 3)其他解法
    • 4)自己的优化代码
    • 5)学到的东西
  • 6、判断链表是否存在环(141、Easy)
    • 1)题目要求
    • 2)我的解法
    • 3)其他解法
    • 4)自己的优化代码
    • 5)学到的东西
  • 7、归并两个有序数组(88、Easy)
    • 1)题目要求
    • 2)我的解法
    • 3)其他解法
    • 4)自己的优化代码
    • 5)学到的东西
  • 8、总结

1、两数之和(167、Easy)

1)题目要求

给定一个已按照升序排列 的有序数组,找到两个数使得它们相加之和等于目标数。

函数应该返回这两个下标值 index1 和 index2,其中 index1 必须小于 index2。

说明:

返回的下标值(index1 和 index2)不是从零开始的。
你可以假设每个输入只对应唯一的答案,而且你不可以重复使用相同的元素。
示例:

输入: numbers = [2, 7, 11, 15], target = 9
输出: [1,2]
解释: 2 与 7 之和等于目标数 9 。因此 index1 = 1, index2 = 2 。

2)我的解法

c++

    vector<int> twoSum(vector<int>& numbers, int target) 
    {
     
        vector<int> a;
        int i=0,j=numbers.size()-1;
        int sum=0;
        while(i<numbers.size()&&j<numbers.size())
        {
     
            sum=numbers[i]+numbers[j];
            if(sum==target)break;
            else if(sum<target)i++;
            else j--;

        }
        a.push_back(i+1);
        a.push_back(j+1);
        return a;
    }

3)其他解法

c++
(1)


class Solution {
     
public:
    vector<int> twoSum(vector<int>& numbers, int target) {
     
        int low = 0, high = numbers.size() - 1;
        while (low < high) {
     
            int sum = numbers[low] + numbers[high];
            if (sum == target)
                return {
     low + 1, high + 1};
            else if (sum < target)
                ++low;
            else
                --high;
        }
        return {
     -1, -1};
    }
};

作者:LeetCode
链接:link.
来源:力扣(LeetCode)

(2)根据有序数组的性质,在第二层开始使用二分法进行查找
时间复杂度为O(nlog^n)
java

    public static int[] twoSum(int[] numbers, int target) {
     

        for(int i = 0; i < numbers.length; i ++){
     
            int l = i + 1;
            int r = numbers.length;
            while(l < r){
     
                int mid = l + (r - l) / 2;
                if(target - numbers[i] > numbers[mid])
                    l = mid + 1;
                else if(target - numbers[i] < numbers[mid])
                    r = mid;
                else {
      //target - numbers[i] == numbers[mid]
                    int[] index = {
     i+1, j+1};//index[0] = i + 1;
                    return index;
                }
            }
        }
     throw new IllegalArgumentException("the input has no solution !");
    }

时间复杂度为O(nlog^n)//无论以多少为底,均记作logn

作者:victor94
链接:link.
来源:力扣(LeetCode)

(3)先用二分法缩小查找范围,缩小范围原理为拆分成的两个数中的较大者不应该大于target-min(numbers)。比如numbers=[2,7,11,23],target=10,只需在[2,7]范围查找即可,因为11>10-2=8,若一个数大于10-2=8,则构造出的数总大于target=10;再比如,numbers=[-2,7,12,23],target=10,只需在[-2,7,12]范围查找即可,因为23>10-(-2)=12。然后再利用双指针在缩小的范围内遍历一次数组获得结果!时间复杂度介于O(lgn)到O(n)之间。

java

class Solution {
     
    public int[] twoSum(int[] numbers, int target) {
     
        if(numbers == null || numbers.length < 2 || target < numbers[0] + numbers[1] || target > numbers[numbers.length - 2] + numbers[numbers.length - 1]){
     
            return null;
        }
        /*缩小查找范围,找到缩小范围的结束边界*/
        int mid = 0;
        if(target - numbers[0] >= numbers[numbers.length - 1]){
     
            mid = numbers.length - 1;
        }else{
     
            int i = 0,j = numbers.length - 1,temp = target - numbers[0];
            while(i <= j){
     
                mid = (i + j) / 2;
                if(numbers[mid] > temp){
     
                    j = mid - 1;
                } else if(numbers[mid] < temp){
     
                    i = mid + 1;
                } else break;
            }
            /*二分结束时mid可能指向第一个大于temp的数,也可能指向第一个小于temp的数*/
            if(numbers[mid] > temp){
     
                mid -= 1;
            }
        }
        /*在缩小的范围内用双指针遍历一次数组获得结果*/
        for(int i = 0, j = mid; i < j;){
     
            if(numbers[i] + numbers[j] > target){
     
                --j;
            } else if(numbers[i] + numbers[j] < target){
     
                ++i;
            } else{
     
                return new int[]{
     i +  1, j + 1};
            }
        }
        return null;
    }
}

作者:xie-5t
链接: link
来源:力扣(LeetCode)

(4)也可以用哈希表在 O(n)时间和 O(n)空间内解决
java

class Solution {
     
    public int[] twoSum(int[] numbers, int target) {
     
        if (numbers == null || numbers.length == 0) {
     
            return new int[1];
        }
        Map<Integer, Integer> map = new HashMap<>();
        int [] result = new int[2];
        for (int i = 0; i < numbers.length ; i ++) {
     
            int temp = target - numbers[i];
            if (map.containsKey(temp)) {
     
                result[0] = map.get(temp);  //其中 index1 必须小于 index2. 所以数组第一个必须是先取map的。顺序不能变
                result[1] = i + 1;
                return result;
            }
            map.put(numbers[i], i + 1);//数字为Key,下标为Value
        }
        return result;

    }
}

时间复杂度:O(n)
空间复杂度:O(n)

作者:diaoliwei
链接:link
来源:力扣(LeetCode)

c++

class Solution {
     
public:
    vector<int> twoSum(vector<int>& numbers, int target) {
     
        int n = numbers.size();
        unordered_map<int,int> Hash;
        for(int i = 0;i < n;++i){
     
            Hash[numbers[i]] = i;//数字为Key,下标为Value
        }
        vector<int> ans;
        for(int i = 0;i < n;++i){
     
            int other = target - numbers[i];
            if( Hash.count(other) && Hash[other] != i ){
     
                ans.push_back(i+1);
                ans.push_back(Hash[other]+1);
                break;
            }
        }
        return ans;
    }
};

作者:django-r
链接:link
来源:力扣(LeetCode)

4)自己的优化代码

c++

  vector<int> twoSum(vector<int>& numbers, int target) 
    {
     
        int i=0,j=numbers.size()-1;
        int sum=0;
        while(i<numbers.size()&&j<numbers.size())
        {
     
            sum=numbers[i]+numbers[j];
            if(sum==target)break;
            else if(sum<target)i++;
            else j--;

        }
        return {
     i+1,j+1};
    }

5)学到的东西

二分查找算法,双指针算法,

哈希表的知识:
(1)c++:unordered_map
unordered_map
count函数
(2)java:Map map = new HashMap<>();
HashMap

vector的用法(push_back()、size())

2、平方数之和(633、Easy)

1)题目要求

给定一个非负整数 c ,你要判断是否存在两个整数 a 和 b,使得 a^2 + b^2 = c。

示例1:

输入: 5
输出: True
解释: 1 * 1 + 2 * 2 = 5

示例2:

输入: 3
输出: False

2)我的解法

c++

 bool judgeSquareSum(int c) 
    {
     
        long long i=0;
        long long j=sqrt(c)+1;
        long long sum=0;
        while(i<=j)
        {
     
            sum=i*i+j*j;
            if(sum==c)return true;
            else if(sum>c)j--;
            else if(sum<c)i++;
        }
        return false;
    }

时间复杂度:O( √C )

3)其他解法

(1)费马平方和定理告诉我们:

一个非负整数 c 能够表示为两个整数的平方和,当且仅当 c 的所有形如 4k+3的质因子的幂次均为偶数。

证明方法可以见 link

因此我们对 c 进行质因数分解,再判断形如 4k+3的质因子的幂次是否均为偶数即可。

java

public class Solution {
     
    public boolean judgeSquareSum(int c) {
     
        for (int i = 2; i * i <= c; i++) {
     
            int count = 0;
            if (c % i == 0) 
            {
     
                while (c % i == 0) 
                {
     
                    count++;
                    c /= i;
                }
                if (i % 4 == 3 && count % 2 != 0)
                    return false;
            }
        }
        return c % 4 != 3;
    }
}

时间复杂度:O( √C )
空间复杂度:O(1)

作者:LeetCode
链接:link
来源:力扣(LeetCode)

(2)在枚举 a 之后,我们也可以直接使用 sqrt 函数直接找出 b。

public class Solution {
     
    public boolean judgeSquareSum(int c) {
     
        for (long a = 0; a * a <= c; a++) {
     
            double b = Math.sqrt(c - a * a);
            if (b == (int) b)
                return true;
        }
        return false;
    }
}

时间复杂度:O( √C )
空间复杂度:O(1)

作者:LeetCode
链接:link
来源:力扣(LeetCode)

(3)我们可以首先枚举 a,并保证 c - a^2 >= 0,随后我们通过二分查找的方法找出是否存在 b,满足 b^2 = c - a^2。二分查找的范围为 [0, c - a^2]。
java

public boolean judgeSquareSum(int c) {
     
        for (long a = 0; a * a <= c; a++) {
     
            int b = c - (int)(a * a);
            if (binary_search(0, b, b))
                return true;
        }
        return false;
    }
    public boolean binary_search(long s, long e, int n) {
     
        if (s > e)
            return false;
        long mid = s + (e - s) / 2;
        if (mid * mid == n)
            return true;
        if (mid * mid > n)
            return binary_search(s, mid - 1, n);
        return binary_search(mid + 1, e, n);
    }

时间复杂度:O(√clog c)​
其中枚举 a 的时间复杂度为 O(√c)
二分查找的时间复杂度为logc。

空间复杂度:O(logc)。
代码中使用递归的方式实现二分查找,也可以优化到 O(1)

作者:LeetCode
链接:link
来源:力扣(LeetCode)

4)自己的优化代码

c++

class Solution {
     
public:
    bool judgeSquareSum(int c) 
    {
     
        long i=0;
        long j=sqrt(c);
        long sum=0;
        while(i<=j)
        {
     
            sum=i*i+j*j;
            if(sum==c)return true;
            else if(sum>c)j--;
            else i++;
        }
        return false;
    }
};

5)学到的东西

双指针算法
费马平方和定理
直接寻找法
枚举法(穷举法)思想:
(1)确定枚举对象、枚举范围和判定条件
(2)枚举可能的解,验证是否为问题的解
枚举法与二分查找法的结合

3、反转字符串中的元音字符(345、Easy)

1)题目要求

编写一个函数,以字符串作为输入,反转该字符串中的元音字母。

示例 1:

输入: “hello”
输出: “holle”
示例 2:

输入: “leetcode”
输出: “leotcede”
说明:
元音字母不包含字母"y"。

2)我的解法

c++

    string reverseVowels(string s) 
    {
     
        if(s.empty())return "";
        int i=0;
        int j=s.length()-1;
        while(i<j)
        {
     
            while(s[i]!='a'&&s[i]!='e'&&s[i]!='i'&&s[i]!='o'&&s[i]!='u'
            &&s[i]!='A'&&s[i]!='E'&&s[i]!='I'&&s[i]!='O'&&s[i]!='U'
            &&i<s.length()-1)
            {
     
                i++;
            }
            while(s[j]!='a'&&s[j]!='e'&&s[j]!='i'&&s[j]!='o'&&s[j]!='u' 
            &&s[j]!='A'&&s[j]!='E'&&s[j]!='I'&&s[j]!='O'&&s[j]!='U'
            &&j>0)
            {
     
                j--;
            }
            if(i<j)
            {
     
                char x=s[i];
                s[i]=s[j];
                s[j]=x;
                i++;
                j--;
            }
        }
        return s;
    }

3)其他解法

c++

  string reverseVowels(string s) {
     
        int i=0,j=s.size()-1;
        string tool="aoeiuAOEIU";
        while(i<j)
        {
     
            while(tool.find(s[i])==-1&&i<j)
                ++i;
            while(tool.find(s[j])==-1&&i<j)
                --j;
            if(i<j)
                swap(s[i++],s[j--]);
        }
        return s;
    }

4)自己的优化代码

c++

string reverseVowels(string s) 
    {
     
        if(s.empty())return "";
        string tool="aeiouAEIOU";
        int i=0;
        int j=s.length()-1;
        while(i<j)
        {
     
            while(tool.find(s[i])==-1&&i<s.length()-1)
            {
     
                i++;
            }
            while(tool.find(s[j])==-1&&j>0)
            {
     
                j--;
            }
            if(i<j)
            {
     
                swap(s[i],s[j]);
                i++;
                j--;
            }
        }
        return s;
    }

5)学到的东西

双指针算法
swap函数用法
string的find函数:如果不包含,返回-1,如果包含则返回下标

4、 最长子序列(524、Medium)

1)题目要求

给定一个字符串和一个字符串字典,找到字典里面最长的字符串,该字符串可以通过删除给定字符串的某些字符来得到。如果答案不止一个,返回长度最长且字典顺序最小的字符串。如果答案不存在,则返回空字符串。

示例 1:

输入:
s = “abpcplea”, d = [“ale”,“apple”,“monkey”,“plea”]

输出:
“apple”
示例 2:

输入:
s = “abpcplea”, d = [“a”,“b”,“c”]

输出:
“a”
说明:

所有输入的字符串只包含小写字母。
字典的大小不会超过 1000。
所有输入的字符串长度不会超过 1000。

2)我的解法

c++

class Solution 
{
     
public:
    void insertsort(vector<string>& d)
    {
     
        int j=0,x=0;
        for(int i=1;i<d.size();i++)
        {
     
            j=i-1;
            while(j>=0&&d[j].length()<=d[i].length())
            {
     
                if(d[j].length()==d[i].length())
                {
     
                    if(d[j]<d[i])break;
                }
                j--;
            }
            string s=d[i];
            int x=i;
            while(x>j+1)
            {
     
                d[x]=d[x-1];
                x--;
            }
             d[j+1]=s;
        }
    }
    bool isbao(string a,string b)
    {
     
        int i=0,j=0;
        while(i<b.length())
        {
     
            while(b[i]!=a[j]&&j<a.length())
            {
     
                j++;
            }
            if(j>=a.length())return false;
            i++;j++;//两个指针都要动!!!
        }
        return true;
    }
    string findLongestWord(string s, vector<string>& d) 
    {
     
        insertsort(d);
        int i=0;
        for(i=0;i<d.size();i++)
        {
     
            if(isbao(s,d[i]))break;
        }
        if(i<d.size())return d[i];
        else return "";
    }
};

3)其他解法

(1)不排序+双指针:

class Solution 
{
     
private:
    bool isZichuan(string target, string s)
    {
        
        //分别从左端开始索引,检测是否为子列
        int i = 0, j = 0;
        while(i < target.size() && j < s.size())
        {
     
            if(target[i] == s[j])
                i++;
            j++;  
        }
        return i == target.size();
    }

public:

    string findLongestWord(string s, vector<string>& d) 
    {
     
        string str = "";
        for(int i = 0; i < d.size(); i++)
        {
     
            int tag = str.length();
            int leng = d[i].length();
            //若字符串更短或者一样长且字母顺序较大的直接舍去
            if(tag > leng || (tag == leng && str.compare(d[i]) < 0))
                continue;
            
            if(isZichuan(d[i], s))
            {
     
                str = d[i];
            }
        }
        return str;
    }
};

作者:lu-guo-de-feng-2
链接:link
来源:力扣(LeetCode)

(2)排序+双重遍历

class Solution {
     
public:
	string findLongestWord(string s, vector<string>& d) {
     

		sort(d.begin(), d.end(), [](string a, string b) {
     
			if (a.size() == b.size()) return a < b;
			return a.size() > b.size();
		});//Lambda表达式

		for (auto str : d) {
     
			int i = 0;
			for (auto c : s) {
     
				if (i < str.size() && c == str[i]) ++i;
			}
			if (i == str.size()) return str;
		}
		return "";
		
	}
};

作者:cyhuang1995
链接:link
来源:力扣(LeetCode)

4)自己的优化代码

c++
排序:

class Solution 
{
     
public:
    bool isbao(string a,string b)
    {
     
        int i=0,j=0;
        while(i<b.length())
        {
     
            while(b[i]!=a[j]&&j<a.length())
            {
     
                j++;
            }
            if(j>=a.length())return false;
            i++;j++;
        }
        return true;
    }
    string findLongestWord(string s, vector<string>& d) 
    {
     
        sort(d.begin(),d.end(),[](string a,string b){
         
            if(a.size()==b.size())return a<b;
            return a.size()>b.size();
        });
        int i=0;
        for(i=0;i<d.size();i++)
        {
     
            if(isbao(s,d[i]))break;
        }
        if(i<d.size())return d[i];
        else return "";
    }
};

不排序:

class Solution 
{
     
public:
    bool isbao(string a,string b)
    {
     
        int i=0,j=0;
        while(i<b.length())
        {
     
            while(b[i]!=a[j]&&j<a.length())
            {
     
                j++;
            }
            if(j>=a.length())return false;
            i++;j++;
        }
        return true;
    }
    string findLongestWord(string s, vector<string>& d) 
    {
     
        int i=0;
        string str="";
        for(i=0;i<d.size();i++)
        {
     
            if(d[i].length()<str.length()||(d[i].length()==str.length()&&d[i]>str))continue;
            if(isbao(s,d[i]))str=d[i];
        }
        return str;
    }
};

5)学到的东西

排序算法:学会了自己手写插入排序

字典序:按ASCII码比较,而不是按字典里的序号进行比较

双指针算法

思想:这种比大小的题也可以不排序,可以一个个枚举进行比较

Lambda表达式,即无名字的可嵌入括号中的函数
c++中的格式:[] (参数) { }

c++中的sort函数
sort的使用
sort的底层实现(插入排序,快速排序,堆排序)

5、回文字符串(680、Easy)

1)题目要求

给定一个非空字符串 s,最多删除一个字符。判断是否能成为回文字符串。

示例 1:

输入: “aba”
输出: True
示例 2:

输入: “abca”
输出: True
解释: 你可以删除c字符。
注意:

字符串只包含从 a-z 的小写字母。字符串的最大长度是50000。

2)我的解法

c++

    bool validPalindrome(string s) 
    {
     
        int i=0,j=s.length()-1;
        int num=2;
        while(i<j)
        {
     
            if(s[i]!=s[j])
            {
     
                if(num==2){
     j--;num--;continue;}//删除后面那个数
                else if(num==1){
     j++;i++;num--;continue;}//把后面那个数恢复,删除前面那个
                else return false;
            }
            else
            {
     
                 i++;j--;
            }
        }
        return true;
    }

3)其他解法

(1)考虑最朴素的方法:首先判断原串是否是回文串,如果是,就返回 true;如果不是,则枚举每一个位置作为被删除的位置,再判断剩下的字符串是否是回文串。这种做法的渐进时间复杂度是 O(n^2) 的,会超出时间限制。

(2)首先考虑如果不允许删除字符,如何判断一个字符串是否是回文串。常见的做法是使用双指针。定义左右指针,初始时分别指向字符串的第一个字符和最后一个字符,每次判断左右指针指向的字符是否相同,如果不相同,则不是回文串;如果相同,则将左右指针都往中间移动一位,直到左右指针相遇,则字符串是回文串。

在允许最多删除一个字符的情况下,同样可以使用双指针,通过贪心算法实现。初始化两个指针 low 和 high 分别指向字符串的第一个字符和最后一个字符。每次判断两个指针指向的字符是否相同,如果相同,则更新指针,令 low = low + 1 和 high = high - 1,然后判断更新后的指针范围内的子串是否是回文字符串。如果两个指针指向的字符不同,则两个字符中必须有一个被删除,此时我们就分成两种情况:即删除左指针对应的字符,留下子串 s[low + 1], s[low + 1], …, s[high],或者删除右指针对应的字符,留下子串 s[low], s[low + 1], …, s[high - 1]。当这两个子串中至少有一个是回文串时,就说明原始字符串删除一个字符之后就以成为回文串。

class Solution {
     
public:
    bool checkPalindrome(const string& s, int low, int high) {
     
        for (int i = low, j = high; i < j; ++i, --j) {
     
            if (s[i] != s[j]) {
     
                return false;
            }
        }
        return true;
    }

    bool validPalindrome(string s) {
     
        int low = 0, high = s.size() - 1;
        while (low < high) {
     
            char c1 = s[low], c2 = s[high];
            if (c1 == c2) {
     
                ++low;
                --high;
            }
            else {
     
                return checkPalindrome(s, low, high - 1) || checkPalindrome(s, low + 1, high);
            }
        }
        return true;
    }
};

作者:LeetCode-Solution
链接: leetcode
来源:力扣(LeetCode)

4)自己的优化代码

c++

    bool validPalindrome(string s) 
    {
     
        int i=0,j=s.length()-1;
        int num=2;
        while(i<j)
        {
     
            if(s[i]!=s[j])
            {
     
                if(num==2){
     j--;num--;continue;}//删除后面那个数
                else if(num==1){
     j++;i++;num--;continue;}//把后面那个数恢复,删除前面那个
                else return false;
            }
            else
            {
     
                 i++;j--;
            }
        }
        return true;
    }

5)学到的东西

双指针算法

贪心算法思想:遵循某种规律,不断贪心地选取当前最优策略。
满足条件:当前最优解即为全局最优解时,贪心成立。

6、判断链表是否存在环(141、Easy)

1)题目要求

给定一个链表,判断链表中是否有环。

为了表示给定链表中的环,我们使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 pos 是 -1,则在该链表中没有环。

示例 1:

输入:head = [3,2,0,-4], pos = 1
输出:true
解释:链表中有一个环,其尾部连接到第二个节点。

示例 2:

输入:head = [1,2], pos = 0
输出:true
解释:链表中有一个环,其尾部连接到第一个节点。

示例 3:

输入:head = [1], pos = -1
输出:false
解释:链表中没有环。

进阶:

你能用 O(1)(即,常量)内存解决此问题吗?

2)我的解法

快慢指针
c++

    bool hasCycle(ListNode *head) 
    {
     
        if(head==NULL)return false;
        ListNode *slow=head;
        ListNode *fast=head;
        do
        {
     
            slow=slow->next;
            if(fast->next==NULL||fast->next->next==NULL)return false;
            else fast=fast->next->next;
        }while(slow!=fast&&slow!=NULL);
        if(slow==NULL)return false;
        else return true;
    }

3)其他解法

方法一:哈希表
思路

我们可以通过检查一个结点此前是否被访问过来判断链表是否为环形链表。常用的方法是使用哈希表。

算法

我们遍历所有结点并在哈希表中存储每个结点的引用(或内存地址)。如果当前结点为空结点 null(即已检测到链表尾部的下一个结点),那么我们已经遍历完整个链表,并且该链表不是环形链表。如果当前结点的引用已经存在于哈希表中,那么返回 true(即该链表为环形链表)。
java

public boolean hasCycle(ListNode head) {
     
    Set<ListNode> nodesSeen = new HashSet<>();
    while (head != null) {
     
        if (nodesSeen.contains(head)) {
     
            return true;
        } else {
     
            nodesSeen.add(head);
        }
        head = head.next;
    }
    return false;
}

作者:LeetCode
链接: link
来源:力扣(LeetCode)

方法二:快慢指针

public boolean hasCycle(ListNode head) {
     
    if (head == null || head.next == null) {
     
        return false;
    }
    ListNode slow = head;
    ListNode fast = head.next;
    while (slow != fast) {
     
        if (fast == null || fast.next == null) {
     
            return false;
        }
        slow = slow.next;
        fast = fast.next.next;
    }
    return true;
}

作者:LeetCode
链接: link
来源:力扣(LeetCode)

4)自己的优化代码

c++

   bool hasCycle(ListNode *head) 
    {
     
        if(!head||!head->next)return false;
        ListNode *slow=head;
        ListNode *fast=head->next;//注意
        while(slow!=fast)
        {
     
            if(!fast->next||!fast->next->next){
     return false;}
            slow=slow->next; 
            fast=fast->next->next;
        }
        return true;
    }

5)学到的东西

哈希表知识:
链接: java:Set nodesSeen = new HashSet<>();
HashSet

快慢指针算法

7、归并两个有序数组(88、Easy)

1)题目要求

给你两个有序整数数组 nums1 和 nums2,请你将 nums2 合并到 nums1 中,使 nums1 成为一个有序数组。

说明:

初始化 nums1 和 nums2 的元素数量分别为 m 和 n 。
你可以假设 nums1 有足够的空间(空间大小大于或等于 m + n)来保存 nums2 中的元素。

示例:

输入:
nums1 = [1,2,3,0,0,0], m = 3
nums2 = [2,5,6], n = 3

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

2)我的解法

双指针
c++

    void merge(vector<int>& nums1, int m, vector<int>& nums2, int n) 
    {
     
        int last=m-1,i=0,j=0;
        if(m==0)
        {
     
            for(int x=0;x<n;x++)
            {
     
                nums1[x]=nums2[x];
            }
            return;
        }
        if(n==0)return;
        while(j<n)
        {
     
            if(nums1[last]<=nums2[j])
            {
     
                last++;
                nums1[last]=nums2[j];
                j++;
            }
            else
            {
     
                i=last;
                while(i>=0&&nums1[i]>nums2[j])
                {
     
                    nums1[i+1]=nums1[i];
                    i--;
                }
                nums1[i+1]=nums2[j];
                last++;
                j++;
            }
        }
        return ;
    }

3)其他解法

方法一 : 合并后排序
直觉

最朴素的解法就是将两个数组合并之后再排序。该算法只需要一行(Java是2行),时间复杂度较差,为O((n+m)log(n+m))。这是由于这种方法没有利用两个数组本身已经有序这一点

class Solution {
     
  public void merge(int[] nums1, int m, int[] nums2, int n) {
     
    System.arraycopy(nums2, 0, nums1, m, n);
    Arrays.sort(nums1);//快排(针对int之类的基本数据类型)和归并排序(针对Object类类型)
    //快排不稳定,但对于基本数据类型,稳定性无意义
  }
}

时间复杂度 : O((n+m)log(n+m))。
空间复杂度 : O(1)。

作者:LeetCode
链接:link
来源:力扣(LeetCode)

方法二 : 双指针 / 从前往后
直觉

一般而言,对于有序数组可以通过 双指针法 达到O(n+m)的时间复杂度。

最直接的算法实现是将指针p1 置为 nums1的开头, p2为 nums2的开头,在每一步将最小值放入输出数组中。

由于 nums1 是用于输出的数组,需要将nums1中的前m个元素放在其他地方,也就需要 O(m)的空间复杂度。
Leetcode_入门_双指针_第1张图片

class Solution {
     
  public void merge(int[] nums1, int m, int[] nums2, int n) {
     
    // Make a copy of nums1.
    int [] nums1_copy = new int[m];
    System.arraycopy(nums1, 0, nums1_copy, 0, m);

    // Two get pointers for nums1_copy and nums2.
    int p1 = 0;
    int p2 = 0;

    // Set pointer for nums1
    int p = 0;

    // Compare elements from nums1_copy and nums2
    // and add the smallest one into nums1.
    while ((p1 < m) && (p2 < n))
      nums1[p++] = (nums1_copy[p1] < nums2[p2]) ? nums1_copy[p1++] : nums2[p2++];

    // if there are still elements to add
    if (p1 < m)
      System.arraycopy(nums1_copy, p1, nums1, p1 + p2, m + n - p1 - p2);
    if (p2 < n)
      System.arraycopy(nums2, p2, nums1, p1 + p2, m + n - p1 - p2);
  }
}

作者:LeetCode
链接:link
来源:力扣(LeetCode)

方法三 : 双指针 / 从后往前
直觉

方法二已经取得了最优的时间复杂度O(n + m)O(n+m),但需要使用额外空间。这是由于在从头改变nums1的值时,需要把nums1中的元素存放在其他位置。

如果我们从结尾开始改写 nums1 的值又会如何呢?这里没有信息,因此不需要额外空间。

这里的指针 p 用于追踪添加元素的位置。

Leetcode_入门_双指针_第2张图片

 public void merge(int[] nums1, int m, int[] nums2, int n) {
     
    // two get pointers for nums1 and nums2
    int p1 = m - 1;
    int p2 = n - 1;
    // set pointer for nums1
    int p = m + n - 1;

    // while there are still elements to compare
    while ((p1 >= 0) && (p2 >= 0))
      // compare two elements from nums1 and nums2 
      // and add the largest one in nums1 
      nums1[p--] = (nums1[p1] < nums2[p2]) ? nums2[p2--] : nums1[p1--];

    // add missing elements from nums2
    System.arraycopy(nums2, 0, nums1, 0, p2 + 1);
  }

作者:LeetCode
链接:link
来源:力扣(LeetCode)

方法四:
大致思路是,从后往前倒序比较两个数组大小,选取大的填充到nums1的尾部,从后往前可以避免移动数组元素。
如果nums2处理完了,num1剩余元素是不需要处理的,所以遍历的时候只需要遍历nums2,但是比较大小之前要判断num1是否处理完,避免数组越界。

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

作者:dong-ming-gao
链接:link
来源:力扣(LeetCode)

4)自己的优化代码

c++

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

5)学到的东西

双指针算法

java知识:
Array.sort函数和System.arraycopy函数
arraycopy

思想:从后往前可以避免移动数组元素。

8、总结

大部分双指针类型题:
i为从前到后指针,j为从后往前
while(i {

i++;j–;

}

你可能感兴趣的:(leetcode入门,LeetCode,算法,leetcode,指针,字符串)