顺序表常见算法题(全)



前言

所有题都来源于力扣的算法题。
我会把所有我刷过的题汇总记录,并且附上我做题的思路,方便我自己复习。
对于其他想刷顺序表相关题目的朋友,可以根据题号自行查阅力扣官网。



题目


1. 删除有序数组中的重复项(26题)

问题描述:给你一个升序排列的数组nums ,请你原地删除重复出现的元素,使每个元素只出现一次,返回删除后数组的新长度。元素的相对顺序应该保持一致。
由于在某些语言中不能改变数组的长度,所以必须将结果放在数组nums的第一部分。更规范地说,如果在删除重复项之后有 k 个元素,那么 nums 的前 k 个元素应该保存最终结果。
将最终结果插入nums的前k个位置后返回 k 。
不要使用额外的空间,你必须在原地修改输入数组 并在使用 O(1) 额外空间的条件下完成。

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

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

思路1:利用双指针

题目的目标是让数据的前面部分为删除后的结果,之后的部分不用管。
可以让一个slow指针记录正待处理的当前位置。用一个fast指针记录待检查的第一个元素的下标
由此可以推出,slow-1是已处理过的最后一个元素的下标
处理和检查是不一样的,处理后的元素都是最终要保留的元素。而原数组的每一个元素都需要检查一遍。

也就是说,slow指针以前的数组元素都是处理后的元素。fast以前的数组元素都已经检查过了,能保留的都已经保留了,不能保留的都被略过了。

由于数组不能有重复项,所以已处理过的任何元素,都不能和其他元素的的值相同。由于数组是升序的,已处理过的任一元素只需要不和前一个元素的值相同即可。所以,如果fast指向的元素不和slow-1指向的元素相同,说明该元素可以留下来,需要把该元素复制到slow指向的元素位置,这样就把它保留下来了;如果fast指向的元素和slow-1指向的元素相同,说明该元素不能留下来,需要略过去。所以,slow的位置不能移动,fast往后移动一位,说明略过去了。

如下图所示:
在这里插入图片描述

/*
 * fast表示待检查的第一个元素的下标
 * slow表示待处理的当前位置
 * slow-1是已经处理过的最后一个元素的下标
 * 
 * 每次需要比较nums[fast] 是否等于 nums[slow - 1]
 * 
 * 如果相等,说明待检查的那个元素不需要加进去,所以待处理的当前位置不需要移动。
 * 也就是slow不用移动,fast+1,就等于把这个元素略过了
 * 
 * 如果不相等,则需要把该元素的值复制到slow的位置,也就是把该元素加进去了
 * 而且slow也需要加1,表示待处理的位置是下一位了
 * 
 * 不管如何,每检查一个元素,fast都是要移动到下一位的
 */
int removeDuplicates(int* nums, int numsSize){
    
    
	// 第1个元素不用处理,也就是a[0]可以略过,所以slow和fast都从1开始
   int slow = 1;
   int fast = 1;
   // fast指向待检查的第一个元素,如果fast = numsSize,说明所有元素都检查完(最后一个元素是nums[numsSize-1])
    while (fast < numsSize)
    {
    
    
    	// 让待检查元素和处理过的最后一个元素比较
    	// 如果值不同,说明该元素可以加入已处理的元素
        if (nums[fast] != nums[slow - 1])	
        {
    
    
            nums[slow] = nums[fast];		// 将待检查元素加入已处理元素
            slow++;							// 加入后,slow++,表示待处理的元素是下一位
        }
        fast++;								// 每检查一个元素,fast往后移动一次
    }
    // slow是处理后的数组的有效长度
    // 因为数组从0开始计数,slow是下一个待处理的元素下标
    // 如果处理完毕,刚好很巧妙的slow这个下标就等于数组的长度值(刚好把下标和长度差的1补上了)
    return slow;							
}

复杂度分析:

  • 时间复杂度:O(n)。需要遍历一遍数组。
  • 空间复杂度:O(1)。只需要2个下标的空间。



2.删除有序数组中的重复项 II(80题)

问题描述:给你一个有序数组 nums ,请你原地删除重复出现的元素,使得出现次数超过两次的元素只出现两次 ,返回删除后数组的新长度。
不要使用额外的数组空间,你必须在原地修改输入数组并在使用 O(1) 额外空间的条件下完成。

示例 1:
输入:nums = [1,1,1,2,2,3]
输出:5, nums = [1,1,2,2,3]
解释:函数应返回新长度 length = 5, 并且原数组的前五个元素被修改为 1, 1, 2, 2, 3 。 不需要考虑数组中超出新长度后面的元素。

示例 2:
输入:nums = [0,0,1,1,1,1,2,3,3]
输出:7, nums = [0,0,1,1,2,3,3]
解释:函数应返回新长度 length = 7, 并且原数组的前五个元素被修改为 0, 0, 1, 1, 2, 3, 3 。 不需要考虑数组中超出新长度后面的元素。

思路1:双指针
具体思路和第一题是一样的。fast指针指向待检查的下一个元素,slow指针指向待处理的当前位置。
唯一需要注意的是,不需要添加元素的条件是nums[fast] = nums[slow-1] = nums[slow-2],而数组是升序排列的。所以,只要nums[fast] = nums[slow-2],则nums[fast]必定等于nums[slow-1],也就是数学中的夹逼准则
当前被检查元素是否需要留下来,只需要和已处理的最后2个元素进行比较即可,进一步说,只需要和已处理的倒数第二个元素进行比较即可

所以,当nums[fast] = nums[slow-2],则略过当前元素,fast移动到下一位即可。
如果nums[fast] != nums[slow-2],则需要把fast指向的元素添加到slow指向的待处理的当前位置上。并且让slow+1,下个位置就变成了待处理的当前位置。

int removeDuplicates(int* nums, int numsSize){
    
    
    if (numsSize <= 2)
        return numsSize;

   int slow = 2;
   int fast = 2;
   while (fast < numsSize)
   {
    
    
       if (nums[fast] != nums[slow - 2])
       {
    
    
           nums[slow] = nums[fast];
           slow++;
       }
       fast++;
   }
   return slow;
}

3.合并两个有序数组(88题)

问题描述:给你两个按非递减顺序 排列的整数数组 nums1 和 nums2,另有两个整数 m 和 n ,分别表示 nums1 和 nums2 中的元素数目。
请你合并 nums2 到 nums1 中,使合并后的数组同样按非递减顺序排列。
注意:最终,合并后数组不应由函数返回,而是存储在数组 nums1 中。为了应对这种情况,nums1 的初始长度为 m + n,其中前 m 个元素表示应合并的元素,后 n 个元素为0 ,应忽略。nums2的长度为 n 。

示例 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 中的元素。

示例 2:
输入:nums1 = [1], m = 1, nums2 = [], n = 0
输出:[1]
解释:需要合并 [1] 和 [] 。
合并结果是 [1] 。

示例 3:
输入:nums1 = [0], m = 0, nums2 = [1], n = 1
输出:[1]
解释:需要合并的数组是 [] 和 [1] 。
合并结果是 [1] 。
注意,因为 m = 0 ,所以 nums1 中没有元素。nums1 中仅存的 0 仅仅是为了确保合并结果可以顺利存放到 nums1 中。

思路1:逆向双指针
这个题,如果从小到大排列就非常困难,但是从大到小排列就很简单
因为数组1的后半部分留有足够的空间,从大到小排列,相当于把数组1和数组2当前较大值移到了一个新的空间(这个空间就是数组1后半部分值为0的空间)。
所以,用一个下标back指向数组1的最后。back指向还未处理的第一个位置(我们是从后往前排序的)。
再用2个下标p1和p2从数组12的最后,不断往前访问。
所以,p1和p2一直指向数组1和2还未访问元素的最大值
我们只需要不断比较p1和p2指向的元素,它们的较大者就可以放到back下标指向的元素。
通过p1和p2不断从后往前遍历2个数组。
如果p2先等于-1,那么p1剩下的元素自然就排好序了,程序就可以结束。
如果p1先等于-1,那么只需要将数组2还未遍历的所有元素,依次复制到数组1的前部,就可以排好序了。

// 程序从后往前处理,依次将当前最大值赋值到数组一的后边空间
void merge(int* nums1, int nums1Size, int m, int* nums2, int nums2Size, int n){
    
    
  int back = m + n - 1;		// back指向还未处理的第一个元素,注意,是从后往前处理的
  int p1 = m - 1;			// p1指向数组1还未访问元素的最大值
  int p2 = n - 1;			// p2指向数组2还未访问元素的最大值
  while (p2 >= 0 && p1 >= 0)
  {
    
    
  	  // 如果p1指向的元素小于等于p2指向的元素,就让p2当前值赋值给back指向的元素
      if (nums1[p1] <= nums2[p2])
          nums1[back--] = nums2[p2--];
      // 如果p1指向的元素大于p2指向的元素,就让p1当前值赋值给back指向的元素
      else
          nums1[back--] = nums1[p1--];
  }
  // 如果p1先遍历完,只需要将p2剩下的元素复制到数组1即可
  if (p1 == -1)
     while (p2 >= 0)
         nums1[back--] = nums2[p2--];
}

猜你喜欢

转载自blog.csdn.net/qq_983030560/article/details/128529156
今日推荐