LeetCode刷题----数组及其双指针

编号35:搜索插入位置
编号27:移除元素
编号26.删除排序数组中的重复
编号15:三数之和
编号18:四数之和
编号206:翻转列表
编号6:零矩阵

编号35:搜索插入位置

参考链接
给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。
你可以假设数组中无重复元素。
示例 1:

输入: [1,3,5,6], 5

输出: 2
示例 2:

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

输出: 1
示例 3:

输入: [1,3,5,6], 7

输出: 4
示例 4:

输入: [1,3,5,6], 0

输出: 0

这道题目,要在数组中插入目标值,无非是这四种情况。

  • 目标值在数组所有元素之前
  • 目标值等于数组中某一个元素
  • 目标值插入数组中的位置
  • 目标值在数组所有元素之后

1.暴力解法:

class Solution {
     
public:
    int searchInsert(vector<int>& nums, int target) {
     
        for (int i = 0; i < nums.size(); i++) {
     
        // 分别处理如下三种情况
        // 目标值在数组所有元素之前
        // 目标值等于数组中某一个元素  
        // 目标值插入数组中的位置 
            if (nums[i] >= target) {
      // 一旦发现大于或者等于target的num[i],那么i就是我们要的结果
                return i;
            }
        }
        // 目标值在数组所有元素之后的情况 
        return nums.size(); // 如果target是最大的,或者 nums为空,则返回nums的长度
    }
};

时间复杂度:O(n)

时间复杂度:O(1)

2.二分法

二分查找涉及的很多的边界条件,逻辑比较简单,就是写不好。

相信很多同学对二分查找法中边界条件处理不好。

例如到底是 while(left < right) 还是 while(left <= right),到底是right = middle呢,还是要right = middle - 1呢?

二分法第一种写法

class Solution {
     
public:
    int searchInsert(vector<int>& nums, int target) {
     
        int n = nums.size();
        int left = 0;
        int right = n - 1; // 定义target在左闭右闭的区间里,[left, right] 
        while (left <= right) {
      // 当left==right,区间[left, right]依然有效
            int middle = left + ((right - left) / 2);// 防止溢出 等同于(left + right)/2
            if (nums[middle] > target) {
     
                right = middle - 1; // target 在左区间,所以[left, middle - 1]
            } else if (nums[middle] < target) {
     
                left = middle + 1; // target 在右区间,所以[middle + 1, right]
            } else {
      // nums[middle] == target
                return middle;
            }
        }
        // 分别处理如下四种情况
        // 目标值在数组所有元素之前  [0, -1]
        // 目标值等于数组中某一个元素  return middle;
        // 目标值插入数组中的位置 [left, right],return  right + 1
        // 目标值在数组所有元素之后的情况 [left, right], return right + 1
        return right + 1;
    }
};

时间复杂度:O(logn)

时间复杂度:O(1)

二分法第二种写法

class Solution {
     
public:
    int searchInsert(vector<int>& nums, int target) {
     
        int n = nums.size();
        int left = 0;
        int right = n; // 定义target在左闭右开的区间里,[left, right)  target
        while (left < right) {
      // 因为left == right的时候,在[left, right)是无效的空间
            int middle = left + ((right - left) >> 1);
            if (nums[middle] > target) {
     
                right = middle; // target 在左区间,在[left, middle)中
            } else if (nums[middle] < target) {
     
                left = middle + 1; // target 在右区间,在 [middle+1, right)中
            } else {
      // nums[middle] == target
                return middle; // 数组中找到目标值的情况,直接返回下标
            }
        }
        // 分别处理如下四种情况
        // 目标值在数组所有元素之前 [0,0)
        // 目标值等于数组中某一个元素 return middle
        // 目标值插入数组中的位置 [left, right) ,return right 即可
        // 目标值在数组所有元素之后的情况 [left, right),return right 即可
        return right;
    }
};

时间复杂度:O(logn)

时间复杂度:O(1)

二分法总结

当循环条件是在一个在左闭右闭的区间里,也就是[left, right],那么middle=right-1,left=middle+1,因为右边是闭区间,返回right+1。
当循环条件是在一个在左闭右开的区间里,也就是[left, right) 。则left= middle+1,right=middle,因为要保证右边是开区间,返回right。

编号27:移除元素

给你一个数组 nums 和一个值 val,你需要 原地 移除所有数值等于 val 的元素,并返回移除后数组的新长度。
不要使用额外的数组空间,你必须仅使用 O(1) 额外空间并原地修改输入数组。
元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素。
示例 1:

给定 nums = [3,2,2,3], val = 3,

函数应该返回新的长度 2, 并且 nums 中的前两个元素均为 2。

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

给定 nums = [0,1,2,2,3,0,4,2], val = 2,

函数应该返回新的长度 5, 并且 nums 中的前五个元素为 0, 1, 3, 0, 4。

暴力解法:双重循环

一个for循环遍历数组元素,第二个for循环更新数组

// 时间复杂度:O(n^2)
// 空间复杂度:O(1)
class Solution {
     
public:
    int removeElement(vector<int>& nums, int val) {
     
        int size = nums.size();
        for (int i = 0; i < size; i++) {
     
            if (nums[i] == val) {
      // 发现需要移除的元素,就将数组集体向前移动一位
                for (int j = i + 1; j < size; j++) {
     
                    nums[j - 1] = nums[j];
                }
                i--; // 因为下表i以后的数值都向前移动了一位,所以i也向前移动一位
                size--; // 此时数组的大小-1
            }
        }
        return size;

    }
};

双指针法

通过一个快指针和慢指针在一个for循环下完成两个for循环的工作。快指针负责发现指定的元素,慢指针负责更新每个位置最终的元素。

// 时间复杂度:O(n)
// 空间复杂度:O(1)
class Solution {
     
public:
    int removeElement(vector<int>& nums, int val) {
     
        int slowIndex = 0; 
        for (int fastIndex = 0; fastIndex < nums.size(); fastIndex++) {
       
            if (val != nums[fastIndex]) {
      
                nums[slowIndex++] = nums[fastIndex]; 
            }
        }
        return slowIndex;
    }
};

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

题目描述

class Solution {
     
public:
    int removeDuplicates(vector<int>& nums) {
     
        if (nums.empty()) return 0; // 别忘记空数组的判断
        int slowIndex = 0;
        for (int fastIndex = 0; fastIndex < (nums.size() - 1); fastIndex++){
     
            if(nums[fastIndex] != nums[fastIndex + 1]) {
      // 发现和后一个不相同
                nums[++slowIndex] = nums[fastIndex + 1]; //slowIndex = 0 的数据一定是不重复的,所以直接 ++slowIndex
            }
        }
        return slowIndex + 1; //别忘了slowIndex是从0开始的,所以返回slowIndex + 1
    }
};

两数之和

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

你可以假设每种输入只会对应一个答案。但是,数组中同一个元素不能使用两遍。

「示例:」

给定 nums = [2, 7, 11, 15], target = 9
因为 nums[0] + nums[1] = 2 + 7 = 9
所以返回 [0, 1]

暴力法

class Solution{
     
	public int[] twoSum(int[] nums , int target ){
     
	for(int i = 0 ; i < nums.length ; i ++){
     
	   if(nums[j] == target - nums[i]){
       
	   	return new int[]{
     i,j};
      }
    }
  }
}

哈希法

为了对运行时间复杂度进行优化,我们需要一种更有效的方法来检查数组中是否存在目标元素。如果存在,我们需要找出它的索引。保持数组中的每个元素与其索引相互对应的最好方法是什么?

哈希表通过以空间换取速度的方式,我们可以将查找时间从 O(n) 降低到 O(1)。哈希表正是为此目的而构建的,它支持以 近似 恒定的时间进行快速查找。我用“近似”来描述,是因为一旦出现冲突,查找用时可能会退化到 O(n)。

但只要你仔细地挑选哈希函数,在哈希表中进行查找的用时应当被摊销为 O(1)一个简单的实现使用了两次迭代。在第一次迭代中,我们将每个元素的值和它的索引添加到表中。然后,在第二次迭代中,我们将检查每个元素所对应的目标元素(target - nums[i]target−nums[i])是否存在于表中。

注意,该目标元素不能是 nums[i]nums[i] 本身!

public int[] twoSum(int[] nums , int target){
     
	Map<Integer,Integer> map = new HashMap<>();
	for(int i = 0 ; i < nums.length; i ++ ){
     
	  map.put(nums[i] , i ) ;
	  }
	 for (int i = 0 ; i < nums.length ; i ++ ){
     
	    int complement = target - nums[i] ;
	    if(map.containsKey(complement) && map.get(complement0 !=-1{
     
	    return new int[]{
     i,map.get(complement)};
	  }
	  throw new IllegalArgumentException("No two sum solution");
	}
}	

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

三数之和

给你一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?请你找出所有满足条件且不重复的三元组。
注意:答案中不可以包含重复的三元组。
示例:
给定数组 nums = [-1, 0, 1, 2, -1, -4],
满足要求的三元组集合为:[[-1, 0, 1],[-1, -1, 2]]

唯一要注意的是去重:选的第一个数不能是之前选过的,选的第二个数和选的第三个数都不难是之前选过的。

哈希法

class Solution {
     
public:
    vector<vector<int>> threeSum(vector<int>& nums) {
     
        vector<vector<int>> result;
        sort(nums.begin(), nums.end());
        // 找出a + b + c = 0 
        // a = nums[i], b = nums[j], c = -(a + b)
        for (int i = 0; i < nums.size(); i++) {
     
            // 排序之后如果第一个元素已经大于零,那么不可能凑成三元组
            if (nums[i] > 0) {
     
                continue;
            }
            if (i > 0 && nums[i] == nums[i - 1]) {
      //三元组元素a去重
                continue;
            }
            unordered_set<int> set;
            for (int j = i + 1; j < nums.size(); j++) {
     
                if (j > i + 2
                        && nums[j] == nums[j-1]
                        && nums[j-1] == nums[j-2]) {
      // 三元组元素b去重
                    continue;
                }
                int c = 0 - (nums[i] + nums[j]);
                if (set.find(c) != set.end()) {
     
                    result.push_back({
     nums[i], nums[j], c});
                    set.erase(c);// 三元组元素c去重
                } else {
     
                    set.insert(nums[j]);
                }
            }
        }
        return result;
    }
};

18.四数之和

LeetCode刷题----数组及其双指针_第1张图片
原理同三数之和,唯一要注意的是去重!

class Solution {
     
public:
    vector<vector<int>> fourSum(vector<int>& nums, int target) {
     
        vector<vector<int>> result;
        sort(nums.begin(), nums.end());
        for (int k = 0; k < nums.size(); k++) {
     
            // 这种剪枝是错误的,这道题目target 是任意值 
            // if (nums[k] > target) {
     
            //     return result;
            // }
            // 去重
            if (k > 0 && nums[k] == nums[k - 1]) {
     
                continue;
            }
            for (int i = k + 1; i < nums.size(); i++) {
     
                // 正确去重方法
                if (i > k + 1 && nums[i] == nums[i - 1]) {
     
                    continue;
                }
                int left = i + 1;
                int right = nums.size() - 1;
                while (right > left) {
     
                    if (nums[k] + nums[i] + nums[left] + nums[right] > target) {
     
                        right--;
                    } else if (nums[k] + nums[i] + nums[left] + nums[right] < target) {
     
                        left++;
                    } else {
     
                        result.push_back(vector<int>{
     nums[k], nums[i], nums[left], nums[right]});
                        // 去重逻辑应该放在找到一个四元组之后
                        while (right > left && nums[right] == nums[right - 1]) right--;
                        while (right > left && nums[left] == nums[left + 1]) left++;

                        // 找到答案时,双指针同时收缩
                        right--;
                        left++;
                    }
                }

            }
        }
        return result;
    }

};

双指针

拿这个nums数组来举例,首先将数组排序,然后有一层for循环,i从下表0的地方开始,同时定一个下表left 定义在i+1的位置上,定义下表right 在数组结尾的位置上。

依然还是在数组中找到 abc 使得a + b +c =0,我们这里相当于 a = nums[i] b = nums[left] c = nums[right]。

接下来如何移动left 和right呢,

如果nums[i] + nums[left] + nums[right] > 0 就说明 此时三数之和大了,因为数组是排序后了,所以right下表就应该向左移动,这样才能让三数之和小一些。

如果 nums[i] + nums[left] + nums[right] < 0 说明 此时 三数之和小了,left 就向右移动,才能让三数之和大一些,直到left与right相遇为止。
时间复杂度:O(n^2)。

class Solution {
     
public:
    vector<vector<int>> threeSum(vector<int>& nums) {
     
        vector<vector<int>> result;
        sort(nums.begin(), nums.end());
        // 找出a + b + c = 0
        // a = nums[i], b = nums[left], c = nums[right]
        for (int i = 0; i < nums.size(); i++) {
     
            // 排序之后如果第一个元素已经大于零,那么无论如何组合都不可能凑成三元组,直接返回结果就可以了
            if (nums[i] > 0) {
     
                return result;
            }
            // 错误去重方法,将会漏掉-1,-1,2 这种情况
            /*
            if (nums[i] == nums[i + 1]) {
                continue;
            }
            */
            // 正确去重方法
            if (i > 0 && nums[i] == nums[i - 1]) {
     
                continue;
            }
            int left = i + 1;
            int right = nums.size() - 1;
            while (right > left) {
     
                // 去重复逻辑如果放在这里,0,0,0 的情况,可能直接导致 right<=left 了,从而漏掉了 0,0,0 这种三元组
                /*
                while (right > left && nums[right] == nums[right - 1]) right--;
                while (right > left && nums[left] == nums[left + 1]) left++;
                */
                if (nums[i] + nums[left] + nums[right] > 0) {
     
                    right--;
                } else if (nums[i] + nums[left] + nums[right] < 0) {
     
                    left++;
                } else {
     
                    result.push_back(vector<int>{
     nums[i], nums[left], nums[right]});
                    // 去重逻辑应该放在找到一个三元组之后
                    while (right > left && nums[right] == nums[right - 1]) right--;
                    while (right > left && nums[left] == nums[left + 1]) left++;

                    // 找到答案时,双指针同时收缩
                    right--;
                    left++;
                }
            }

        }
        return result;
    }
};

206.翻转链表

题意:反转一个单链表。
示例:
输入: 1->2->3->4->5->NULL

输出: 5->4->3->2->1->NULL

双指针

LeetCode刷题----数组及其双指针_第2张图片

首先定义一个cur指针,指向头结点,再定义一个pre指针,初始化为null。
然后就要开始反转了,首先要把 cur->next 节点用tmp指针保存一下,也就是保存一下这个节点。
为什么要保存一下这个节点呢,因为接下来要改变 cur->next 的指向了,将cur->next 指向pre ,此时已经反转了第一个节点了。

接下来,就是循环走如下代码逻辑了,继续移动pre和cur指针。

最后,cur 指针已经指向了null,循环结束,链表也反转完毕了。 此时我们return pre指针就可以了,pre指针就指向了新的头结点。

class Solution {
     
public:
    ListNode* reverse(ListNode* pre,ListNode* cur){
     
        if(cur == NULL) return pre;
        ListNode* temp = cur->next;
        cur->next = pre;
        // 可以和双指针法的代码进行对比,如下递归的写法,其实就是做了这两步
        // pre = cur;
        // cur = temp;
        return reverse(cur,temp);
    }
    ListNode* reverseList(ListNode* head) {
     
        // 和双指针法初始化是一样的逻辑
        // ListNode* cur = head;
        // ListNode* pre = NULL;
        return reverse(NULL, head);
    }

};

LeetCode刷题----数组及其双指针_第3张图片
暴力法:
时间复杂度:O(nm)O(nm)O(nm)。二维数组中的每个元素都被遍历,因此时间复杂度为二维数组的大小。

空间复杂度:O(1)O(1)O(1)。

线性快速查找法

选左上角,往右走和往下走都增大,不能选
选右下角,往上走和往左走都减小,不能选
选左下角,往右走增大,往上走减小,可选
选右上角,往下走增大,往左走减小,可选

以从右上角为例,从二维数组的右上角开始查找。如果当前元素等于目标值,则返回 true。如果当前元素大于目标值,则移到左边一列。如果当前元素小于目标值,则移到下边一行。

class Solution{
      
   public boolean findNumberIn2DArray(int[][] martix, int target) {
     
     if(matrix==null || martix.length==0 || martix[0].length == 0){
     
     	     return false ;
     	     }
     int x = martix.length , y = martix[0].length ;
     int row = 0 , cloumn = martix[0].length-1 ;
     while(row < x && cloumn>=0){
     
           if(martix[row][cloumn] == target){
     
           	return true;
           	}
           else if(martix[row][cloumn] < target){
     
           	row++;
           	}
           else{
     
           	cloumn -- ;
           	}
     }
        return false;
  }
}

08.零矩阵

LeetCode刷题----数组及其双指针_第4张图片
思路:拿第一行和第一列做标记,来标记该行或者该列是否需要均清零。
最后来判断第一行和第一列是否需要均清零

import java.util.Map;
import java.util.HashMap;
class Solution {
           
    public void setZeroes(int[][] matrix) {
             
	    boolean flagRow = false;         
	    boolean flagColumn = false;         
	for(int i = 0 ; i < matrix.length ; i ++){
       
	    if(matrix[i][0] == 0)  flagRow = true ;      
	     }        
	for(int i = 0 ; i < matrix[0].length ;i++){
               
	    if(matrix[0][i] == 0) flagColumn =true;        
	     }        
	for(int i = 1 ; i < matrix.length ; i ++ ){
                 
	    for(int j = 1 ; j < matrix[0].length ; j ++ ){
                     					 
	         if(matrix[i][j] == 0){
                       
	               matrix[0][j] = 0;                  
	               matrix[i][0] = 0;               
	                }         
	      }       
	 }        
	 for(int i = 1 ; i < matrix.length ; i ++ ){
                
	      for (int j = 1 ; j< matrix[0].length ; j ++ ){
     
	         if(matrix[0][j] == 0 || matrix[i][0] == 0 ){
                         
	              matrix[i][j] = 0 ;              
	                 }           
	      }      
         }      
         if(flagRow){
               
           for(int i = 0 ; i < matrix.length ; i ++){
                      
                 matrix[i][0] = 0;       
           }        
        }        
         if(flagColumn){
               
            for(int i = 0 ; i < matrix[0].length ;i++){
                     
                matrix[0][i] = 0 ;        
            }        
        }    
    } 
 }

你可能感兴趣的:(LeetCode,leetcode,二分法,数据结构,数组,双指针)