LeetCode数组专栏(简单:1-20)

为了找工作不得不加油刷题呀o(╥﹏╥)o,但是Leetcode的题刷完就忘,所以特开一帖,记录我的刷题过程。每道题目标题后边的括号中对应的leetcode的题号。
这一遍我打算使用js去刷,因为当前的目标是找到一个前端的实习,找到实习之后就可以没事去和朋友玩耍了(*^ ▽ ^*),附上刷题网址:https://leetcode-cn.com/problems


  1. 两数之和(1)
    这道题是给定了一个nums
      这道题我一开始用的蛮力法,有就是说遍历整个nums,然后每次都算出remainder = target - nums[i]。再从剩下的数组中寻找,看是否有remainder。可以看到这样的效率为O(n^2)。
      即便这种简单题,第一次刷我也想不出最好的办法,于是就去看了采纳数最多的方法:
    在这里插入图片描述
      这个方法时间复杂度为O(n),空间复杂度上借助了一个对象,用来存储每次遍历的差值。从根本上来讲还是利用了以空间换时间的技巧。

  1. 删除排序数组中的重复项(26)
    在这里插入图片描述
      这道题我有思路,但是编码能力弱的我还是忍不住看了部分答案才做出来,不过有进步就是好的!
    在这里插入图片描述
      这道题没什么技巧,需要审题,给定的是有序的数组,并且不能使用额外的数组空间,并不是不让你使用额外的空间Σ(⊙▽⊙"a。还需要注意最后返回的数组长度为i+1,这个稍微想一下就能想通。

  1. 移除元素(27)
    在这里插入图片描述
      下面是我首次的提交,但是很遗憾,虽然能正常通过给定例子的测试。但是提交的时候没通过,原因是当nums[j]与val相等时,nums[j] = nums[j+1]这一项在这个条件下乍一看是对的,但是当nums[j] !== val的时候,数组的处理没有考虑。

在这里插入图片描述
在这里插入图片描述
  所以这里的正确操作应该是当nums[j]与val相等时,调用数组方法splice,将对应位置的值从数组中删除(就是正常的操作,没有一点绕弯。。):
在这里插入图片描述
这里回忆一下splice函数:
注意要与字符串方法slice区分(slice为截取字符串的子串的方法)
splice() 方法向/从数组中添加/删除项目,然后返回被删除的项目。

splice(index,howmany,item1,item2…)
在这里插入图片描述


  1. 搜索插入位置(35)
    在这里插入图片描述
      下面是我最开始的做法,很明显没有考虑边界的情况
    在这里插入图片描述
      下面考虑边界情况后:
    在这里插入图片描述
    在这里插入图片描述
      说我超出时间限制。。。
    没办法,只能借鉴一下大佬们的做法了o(╥﹏╥)o:
    在这里插入图片描述
      纳尼!,我居然就因为最后少写了一个默认的return nums.length。但是我注意到时间上并不理想(因为这种方法是暴力解法)。

换一种方法再试试:
这道题是一个数和排好顺序的数组中的每个数进行比较,很显然,这种情况下,用二分查找是最好的(具体原因自行百度)。
在这里插入图片描述
以下是对二分查找的优化,值得借鉴。代码执行结果吊炸天了!这部分参考:

https://leetcode-cn.com/problems/search-insert-position/solution/te-bie-hao-yong-de-er-fen-cha-fa-fa-mo-ban-python-/

在这里插入图片描述
  在这里再写一下二分查找的模板:

  1. 首先把循环条件写成while(left<right),这样,循环退出的时候一定有left === right。这样就不用最后判断返回left还是right了。
  2. 中位数写成let mid = (left + right) >>> 1。
    中位数的解释:当数组元素是偶数时,中位数分为左中位数和友中位数。
    使用 int mid = left + (right - left) / 2 ; 得到左中位数的索引;
    使用 int mid = left + (right - left + 1) / 2 ; 得到右中位数的索引。
  3. 先写逻辑上容易想到的分支逻辑,这个分支通常是排除中位数的逻辑。
  4. 循环内只写两个分支,一个分支排除中位数(逻辑容易的那条分支),另一个分支不排除中位数,循环中不单独对中位数作判断。
    两条分支比三条分支好的原因:不用在每次循环开始单独考虑中位数是否是目标元素,节约了时间,我们只要在退出循环的时候,即左右区间压缩成一个数(索引)的时候,去判断这个索引表示的数是否是目标元素,而不必在二分的逻辑中单独做判断。-------这也是改进后效率高的原因
    针对容易的分支,写出的代码有两种:
//左边好想一点
if(排除中位数的逻辑){
	//排除中位数
	left = mid + 1;
}else{
	//右侧不排除中位数
	right = mid;
}

//右边好想一点
if(排除中位数的逻辑){
	//排除中位数
	right = mid + 1;
}else{
	//左侧不排除中位数
	left = mid;
}
  1. 根据逻辑选择中位数的类型,可能是左中位数、也可能是右中位数。选择的标准是避免死循环
    死循环容易发生在区间只有 2个元素时候,看下面例子:
	//左中位数
	int mid = (left + right) >>> 1;
	if(排除中位数的逻辑){
		right = mid + 1;
	}else{
		left = mid;
	}
	//右中位数
	int mid = (left + right + 1) >>> 1;
	if(排除中位数的逻辑){
		right = mid + 1;
	}else{
		left = mid;
	}

上述两个例子都会在数组元素还剩两个的时候出现死循环,如:[1,2]:left = 0,right = 1,选取左中位数lmid = (0 + 1)/2 = 0,此时进入循环中的判断,如果条件成立,right = 0 + 1 = 1,如果条件不成立,left = mid = 0,可以看到无论如何left和right都不会继续“收缩”,所以会导致死循环。右中位数同理。
  所以,在选择中位数的时候,遵循:如果每次是收缩左边界(left = mid - 1),那么就选左中位数;如果每次收缩右边界(right = mid + 1),那么就选右中位数。很显然,这样选的目的是为了在最后剩2个数的时候,能使得左边界或右边界继续收缩。

  1. 退出循环的时候,可能需要对“夹逼”剩下的那个数单独做一次判断,这一步称之为“后处理”。
      二分查找法之所以高效,是因为它利用了数组有序的特点,在每一次的搜索过程中,都可以排除将近一半的数,使得搜索区间越来越小,直到区间成为一个数。那么有个疑问:“区间左右边界相等(即收缩成 1 个数)时,这个数是否会漏掉”,解释如下:
    (1)、如果你的业务逻辑保证了你要找的数一定在左边界和右边界所表示的区间里出现,那么可以放心地返回 left 或者 right,无需再做判断;

    (2)、如果你的业务逻辑不能保证你要找的数一定在左边界和右边界所表示的区间里出现,那么只要在退出循环以后,再针对 nums[left] 或者 nums[right] (此时 nums[left] == nums[right])单独作一次判断,看它是不是你要找的数即可,这一步操作常常叫做“后处理”。

  2. 取中位数的时候,要避免在计算上出现整型溢出
    写中位数有三种方式:
    (1) let mid = (left + right)/2
    (2)let mid = left + (right - left)/2
    (3)let mid = (left + right) >>> 1
    以上三种写法在中间值不出现数值溢出的时候都没问题。但是当left+right溢出时,第一个就不对了,当right很大(但还没溢出),left很小,而right-left溢出时,第二个也不对了,但是对于第三种写法:我们知道计算机中无符号右移,是丢弃右侧指定位数,对左侧补0,也就是说,对于正数,无符号左移就是除操作(移1位就是除以2,移2位除以4。。),对于负数,无符号移位后就会变成正数。所以即便left+right数值溢出(变成负数–左侧多一位1),我们也可以通过移位将左侧补0,从而变成正数。
    最后给出模板(左中位数为例,右中位数同理):


while(left < right){
	let mid = (left + right) >>> 1;
	if(排除中位数逻辑){
		left = mid + 1;
	}else{
		right = mid;
	}
}
//根据业务逻辑return(以leetcode的35题为例)---left和right最后相同,return两个任何一个都对
return left;

  1. 最大子序和(53)
    在这里插入图片描述
      说实话,这道题我只能想到暴力求解,所以直接看了别人的解法:
    在这里插入图片描述
      这个方法设置了一个sum来记录字串和,一个ans存最终的结果。当字串和sum小于0时,代表sum对结果无增益效果,需要舍弃;当sum大于0时,表示对结果又增益效果,ans一定存在sum中。
      PS:这道题难度应该middle吧。。。据说是清华考研题。根据题目字眼“连续”,“最大”可以认定八九不离十是一道DP问题。可以找到递推关系:f(n) = max(f(n-1) + A[n], A[n]);

这道题的难点在于利用分治法去解决,分治法的复杂度(O(NlogN))实际上并没有DP好,不明白为何官方还说分治精妙—或许是思想?(ˇˍˇ) ~。
  对于这道题,其实就是他的子序不是在整个序列的左半边就是在右半边,或者穿过中间。对于左右半边的情况,是一样的,可以使用递归进行处理,对于中间,可以直接计算出来:
在这里插入图片描述
注:在写这个代码的时候我参考了某大神用python写的代码,中间遇到了一个问题,那就是在python(java也是)中整数除法得到结果的是一个整数。但是在js中,是没有整数这一说的,类型为Number(为32位双精度类型,相当于java中的double),所以我们所谓的整型,比如9,在js里其实是9.0,只不过小数点后边的0默认省去的而已。所以在计算除法的时候需要用Math.floor()或者强制类型转换parseInt()来达到和python与java中相同的效果。


  1. 加一(66)
    在这里插入图片描述
      这道题完全是自己做的,错了两次,最后AC,还是能感觉到自己的进步,加油ヾ(◍°∇°◍)ノ゙。(虽然代码写的确实不够漂亮)
    在这里插入图片描述
    以下是相对优雅的代码:使用了取余的技巧,当nums[i]不为10时,对10取余均为本身,此时,不需要考虑进位,在尾数+1之后直接返回即可;当nums[i]为10时,对10取余得0,此时需要考虑进位,从而继续循环,对前一位+1(进位);如果数组值本身全为9,那么最后数组得到的会全是0,并且在循环结束前不会返回,所以最后需要在数组首位添加一个元素1,并返回得到的长度+1的数组。
    在这里插入图片描述

  1. 合并两个有序数组
    在这里插入图片描述
      很遗憾,这道题我自己有思路,但是没能写出来,看了老外的1 Line解法。我默默的献出了我的膝盖。
    在这里插入图片描述
    代码大致的意思是:把nums2数组依次插入到nums1“母数组”的对应位置。两个数组从后向前比较,看似循环了n次,其实nums1[m+n-1]一共被赋值了m+n次,也就是两个数组之和。
    在这里插入图片描述
    上面这段代码可能看起来逻辑更加清晰一点。

  1. 杨辉三角(118)
    在这里插入图片描述
    在这里插入图片描述
      思路很好想,本科的时候上C语言课就写过,只不过用js写,二维数组的构造不太熟悉,参考了别人子循环中的subArr构造的代码。
    在这里插入图片描述
    这道题很常规,就不赘述了。

  1. 杨辉三角II(119)
    在这里插入图片描述
      这道题和上道题最大的不同在于,你需要在时间复杂度为O(N)下算出第k行的数组。我们需要找出res[i]与res[i-1]的递推关系,从而解决这道题。还需要注意参数rowIndex为索引。
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在修改了之后代码可读性更好了。其中i代表从上向下遍历的数组层数(从第0层开始遍历);或者也可以理解为对最后的res数组,从前向后依次求解每项。每次计算所需要的上一次数字值保存在pre中。
    在这里插入图片描述
      常规做法为O(N^2):
    在这里插入图片描述
      这段代码可能内层不太好理解,外层的for循环可以理解为每次循环是计算当前层的值(因为计算结果必须需要用到上一层的数组值)。内层的循环中j代表当前层所要更新的元素位置;例如在“第三索引层”,结果为 1 3 3 1,;由于最后循环结束之后,会push一个固定等于1的尾元素,所以我们循环中得到1 3 3即可。显然需要更新的元素有两个,那么应当控制内层循环2次,分别更新res[2]与res[1]。在“第二索引层”中,值为1 2 1,根据杨辉三角公式,可以得知,res[2] +=res[1],其中更新后的res[2] = 3为当前层元素,res[1] = 2与更新前的res[2] = 1为上一层的元素。
      这样,外层循环从上到下,内层循环在当前层,从右向左依次更新新值,实现了动态更新结果的目的。

  1. 买卖股票的最佳时机(121)
    在这里插入图片描述
      这道题一开始没思路,但是后来看了别人的解析(没点进去),说这道题和前面的最长字串类似,于是自己想了一会儿,写出来了:
    在这里插入图片描述
      方法还可以简化,省去一些不必要的判断分支以及一些不必要的变量,如下:
    在这里插入图片描述
      值得注意的是minprice代表当前的最低价格,初始值一定要让它为最大数。该方法较上面的方法,额外空间减少了一个,并且分支判断降低(只在当前差值大于当前最大值或当前值小于当前最小值点的时候进入分支)。

  1. 买卖股票的最佳时机II(122)
    在这里插入图片描述
      我想这道题的大概意思就如我下图所画的,求整个“函数”的递增区间。所以可以设一个pre记录当前点,设一个end记录末位点。end一直向后遍历,直到区间开始递减。同时在递减区间,同步更新pre为遍历的当前元素,直到递增区间开始。
    在这里插入图片描述
      代码如下(实际逻辑上还是有一些错误),但是突然一看,end完全没必要存在。。。
    在这里插入图片描述
      于是有了下面的代码:
    在这里插入图片描述
    这里需要注意每次循环中的加操作并不会耗用过多的时间,所以没有为了追求减少操作而去每次加“增区间”。完全可以在循环过程中只要当前较上一次循环增加,就对总数加即可。这样会使逻辑简单清晰很多。而实际上,时间复杂度也没有提升。

  1. 两数之和II(167)
    在这里插入图片描述
      这道题看到与第一题的区别仅仅在于这里是已经排序。所以首先想到的一定是二分查找,而不是继续使用第一题的暴力解法或者建立hash。
      具体做法大概是:外层对所有数进行循环,每次遍历过程中,在剩下的数(当前数的右侧,因为已经遍历过的数不可能是后面数的差了----在这之前的循环已经验证过了)中进行二分查找,看target-nums[i]是否存在。时间复杂度为O(N*logN)。
    在这里插入图片描述
    PS:上面的代码看似很容易,但是细节问题全部注意到并能AC也不是那么简单的。
    下面的解法就是效率更高的双指针解法,代码简单易懂,时间复杂度为O(N)。
    在这里插入图片描述

  1. 求众数(169)
    在这里插入图片描述
    该题比较简单(看到统计数组中数字出现次数这类题的时候自然而然的想到建立Hash),需要注意的是只有一个数的情况下单独判断下,或者在循环中进行控制:
    在这里插入图片描述
      还有另外一个比较精妙的方法:排序。把数组进行排序,取排序后数组的中间值,即为所求:
    在这里插入图片描述

  1. 旋转数组(189)
    在这里插入图片描述
      拿道题目后我第一个想法就是使用slice,将数组切成两段,然后再进行拼接。在vscode中成功运行,但是在Leetcode中得不到结果,可能这类题目,系统不让使用内部方法。于是想了其他方法:
      第一种方法可以使用暴力解法,没什么可说的,数组暴力的调整k次,每次数组中所有元素集体“右移”1次。这种做法很浪费时间。
    在这里插入图片描述
      第二种做法是使用额外的数组(以空间换取时间)。我们只需要循环一次,每次将原数组的元素放置到额外数组对应正确的位置即可(放到(i+k)%nums.length处):
    在这里插入图片描述
      这里最开始我直接返回extraArray,leetcode依然没有返回正确结果,而是返回nums最初的数组值。到这里我才恍然大悟,原来这道题是需要在原数组的基础上进行改变,我使用slice和concat都是建立了新数组。所以我再使用我刚才的方法跑了一次:
    在这里插入图片描述
      这里需要注意slice、splice、concat、push等数组方法的细节。slice和concat不会改变原数组,题目要求需要返回原数组,不能是新数组,所以需要借助splice及push方法这类能改变原数组的方法。执行后结果还是不对!!!看到错误用例可以明白,这道题有可能移动的次数k大于数组长度,这样就会出现重复移动数组的情况。所以我们需要对k进行处理,使其变为最小的循环数字(k%nums.length):
    在这里插入图片描述
      第三种做是环状替代。第二种方法还是有缺陷,那就是每次把原数组的元素移动到额外数组的时候,其实相当于损失了原数组对应与额外数组同等位置的存储空间。n次移动就会造成n此空间的损失,所以会多出来N个空间(也就是额外数组的空间);这里我们可以用一个空间temp,在每次移动数组元素的时候把移动至位置(设为a)的原元素存到temp里,然后再移动原元素到b,b在存到temp覆盖掉a(a已经找到了它的归属,所以它的值也就没有必要保存了),依次环状的移动下去,就可以实现移动n次,却只需要O(1)的空间:
    在这里插入图片描述
      这个方法特别需要注意一个特殊情况,如下:移动了nums.length/k次后元素又回到初始点(1),这种k刚好被整除的情况下,就需要在回到原始点后往后移动一个位置,而我们可以设一个计数值count,必须整体移动数组长度次才认定结束。所以最外层的循环加的意义也就在于此。(start控制回到原点后向后移动)
    nums: [1, 2, 3, 4, 5, 6]
    k: 2
    在这里插入图片描述
      第四种:整体翻转。这个方法很重要,这道题主要就是为了考察这个方法!
    在这里插入图片描述
    这个方法看上面的图可以轻易看懂,下面是具体代码:
    在这里插入图片描述

  1. 存在重复元素(217)
    在这里插入图片描述
      第一种方法没什么好说的,利用Hash,建立每个元素的索引,代码如下:
    在这里插入图片描述
      暴力法这里就不写出来来了,比较容易,并且没什么卵用。第二种方法是排序:对数组进行排序后,相同的元素一定是邻近的,这样就可以对排序后的数组进行一次遍历查看是否有相同元素。代码如下:
    在这里插入图片描述
    这里补充下复杂度:
    (1)BF时间复杂度为O(N^2),空间O(1)
    (2)Hash时间复杂度为O(N),空间O(N)—空间来源于建立Hash表
    (3)排序时间复杂度为O(N*logN),空间O(1)—时间来源于快速排序NLogN与循环遍历的N求最大值(因为sort和循环是并列关系,所以取较大的复杂度NlogN,也就是说算法的时间主要耗费在排序上)

    PS:V8 引擎 sort 函数只给出了两种排序 InsertionSort 和 QuickSort,数量小于22的数组使用 InsertionSort,比22大的数组则使用 QuickSort。

  1. 存在重复元素II(219)
    在这里插入图片描述
      这道题最大的坑点在于对于题目意思的理解。题目说的是在数组中存在这样的i和j,它们的值相同,且索引差的绝对值小于k。也就是说我们需要找到最近的两个相同的数,看它们的索引查是否小于k。这个题意的转化我觉得很有必要。
      思路:我们可以设置一个Set或者数组,使它的长度适中控制在小于k之下。每次遍历整个给定数组的同时,将数组新元素插入到这个Set或者数组中去,同时删除“最远”的元素,来保持待测数组或Set的长度小于等于k。最后我们只需要每次循环时看看在这个待测数组或Set中是否有两个相同的元素即可。
      代码如下:
    在这里插入图片描述
    这个执行时间真的醉了。。。还是用Hash吧。
    在这里插入图片描述
      使用Hash的代码:
    在这里插入图片描述

  1. 缺失数字(268)
    在这里插入图片描述
      第一种方法是排序:需要注意的是sort函数不能对数组中值按大小进行排序(而是按UniCode码排序的),所以必须选择一个能按数值排序的函数。
    在这里插入图片描述
      第二种方法是求和相减,只要知道等差数列求和公式的应该都能轻易写出来代码:
    在这里插入图片描述
      第三种方法是使用Set集合存储所有元素,然后遍历数组中的每个元素去与Set库进行比对,看哪个元素不在库里:
    在这里插入图片描述
    PS:这里注意Set判断元素是否在自己里面的方法是has,这与java的contains方法一样,但是名字需要特殊记忆。

  第四种方法是比较新颖的一个方法:位运算。我们利用异或的一个特性:对一个数进行异或两次会得到与这个数相同的结果。
在这里插入图片描述
大概的思路就是用数组下标和数组元素异或,如果没有缺失值,最后结果一定为0。因为数组下标和元素值都是0到nums.length。所以最后异或的结果一定是0^缺失值(其中0是未缺失值和对应同值得数组下标两两配对,从而抵消),最终得到缺失值。
在这里插入图片描述


  1. 移动零(283)
    在这里插入图片描述
      这道题属于“数组变换”的范畴,是数组算法的重点
    这道题需要我们解决两个问题:
    (1)把所有的0放到数组末尾
    (2)保持其他非零元素相对位置不变
    我们可以分别解决两个问题,然后再“合一起”。
      第一种方法是暴力的引入额外数组,将元素按要求搬到额外数组上,然后把额外数组再复制回所求数组(实际上,这种方法不符合题意的“只在原数组上进行操作”)。
    在这里插入图片描述
      第二种方法是针对第一种方法,进行空间上的优化,也就是为了符合题意,在每次移动非零数的时候找到非零数应该到达的位置(也就是lastNonZeroFoundAt):
    在这里插入图片描述
      第三种方法是针对第二种方法进一步进行改造,在时间上优化。
    在这里插入图片描述
      具体思想通过下图一看便知。
    在这里插入图片描述

  1. 第三大的数(414)
    在这里插入图片描述
    在这里插入图片描述
      我想出的办法就是设立三个值,分别存储最大、次大、第三大的三个数。然后通过一系列判断得出最后的第三大的值:
    在这里插入图片描述
      这里注意Math.max(…nums)的写法。这种写法可以把nums中的值全都作为参数传max函数中。
    在这里插入图片描述
      代码结构看起来不够好,优化代码结构之后:
    在这里插入图片描述

  1. 找到所有数组中消失的数字(448)
    在这里插入图片描述
      这道题注意不能使用额外空间。题目条件有些苛刻,一开始我想用Set先去重,然后再找缺失。。但是想到不能使用额外空间,所以没继续写下去,就看了解析。这道题使用抽屉原理+异或运算的特性
    抽屉原理(也称桶排序、鸽巢原理):
      抽屉原理的一般含义为:“如果每个抽屉代表一个集合,每一个苹果就可以代表一个元素,假如有 n + 1 个元素放到 n 个集合中去,其中必定有一个集合里至少有两个元素。”
    详细参考:https://www.cnblogs.com/ECJTUACM-873284962/p/6935506.html

PS: 基数排序和桶排序的区别:
基数排序是针对不同数字,从个位开始,进而十位、百位一点点的放入10个数字桶中;而桶排序则是将不同的数字放到不同的桶中(桶的数量取决于数字的大小区间)。

异或特性
  不使用额外的空间,可以使用“异或运算”代替。
  “基于异或运算”利用了“异或运算”是不进位的二进制加法。它有如下性质:

如果 a ^ b = c ,那么 a ^ c = b 与 b ^ c = a 同时成立。

所以交换两个变量的值,比如a和b,可以这么做:
a = a ^ b
b = a ^ b
a = a ^ b

代码如下:
在这里插入图片描述
  这里面最难理解的代码就是第一个循环中的while循环。这段代码看起来像是循环嵌套,时间复杂度为O(N^2),其实不然。这里用到了桶排序的思想,使得所有元素在遍历过一次之后就被放到了自己的位置(或者“无处安放”)。为了更好的理解这段,下面通过程序调试截图来展现:

  待测数据如下:
在这里插入图片描述
(1)第一次遍历之前可以看到nums[0] = 4,没有在自己应该在的位置(索引nums[i] - 1 = 3)。所以把他移动到索引3的位置,而还要保证索引3位置的元素不丢失。所以交换他们的位置(4和7交换)。
在这里插入图片描述
(2)可以看到移动后的元素位置如下图。但是num[0]此时的元素值7还没有到自己应该在的位置(6),所以继续移动。将7与索引6位置的3交换。
在这里插入图片描述
(3)交换后的位置如下图。nums[0]此时等于3,3应该在索引2的位置,继续交换。
在这里插入图片描述
(4)交换后的位置如下。nums[0]此时为2,他应该在索引位1的位置,继续交换。
在这里插入图片描述
(5)交换后的位置如下,nums[0]此时为3,可以看到3已经在索引2的位置了(数字值重复了),结束while循环,外层循环进入下一次(i=1)。
在这里插入图片描述
(6)i=1、i=2、i=3的时候元素都已经到了“应该到的位置”了。所以不进内层的while循环。如果细心观察的话,其实经过i=0的while循环过后,一共交换了4次位置,而得到的中间结果(也就是上图5),已经有4个元素到达了“应该到的位置了”。
(7)i=4的时候。nums[4]的值为8,它没有到达应该到的位置(索引7的位置),所以进行交换。
在这里插入图片描述
(8)交换后,nums[4] = 1 ,继续交换:
在这里插入图片描述
(9)交换后nums[4] = 3 ,3已经在索引2的位置,结束当前循环。
(10)i = 5、 i = 7、 i = 8的时候元素都已经到达指定位置,所以最终得到的序列为图8的结果。此时我们需要做的是将nums[i] !== i+1的元素作为结果输出。也就是位置4和位置5应该对应的元素5和6。这样我们就找到了缺少的元素了。

  第二种方法:
在这里插入图片描述
  这种方法用的也是桶排序的思想。即每个元素值肯定对应一个唯一的“桶”。所以在遍历所有元素的过程中,每次计算出每个数字应该放置位置的索引,然后对索引位的值加数组长度(这样做的好处是不会影响之后循环中计算元素索引-----通过取余即可),出现多次的元素,对应索引位一定是加了多次数组长度,而未出现的元素对应索引位一定是小于等于数组长度的(为什么还会等于呢?因为最大的数如果恰好待在丢失的索引位置,这样那个位置从始至终没有执行加数组长度的操作,而它的值却是最大的数===数组长度)。

  第三种方法:
在这里插入图片描述
  第三种方法同样也是利用所谓的桶排序思想。遍历数组中每一个元素,计算出每个元素应该出现在的索引位置,然后将对应索引位置的值取负。最后数组中正值对应的位置就是缺失元素。值得注意的是需要注意因为会缺失值,所以有的值肯定会重复,也就可能会导致在那些重复值对应的索引位置出现负负得正的情况,所以要保证每个索引位只取一次负,如果索引位已经为负值,表示该索引对应值已被处理,无需再取负。
  这道题的三个解法实际上都是一个思路,那就是先计算出数组中每个值“应该出现”的索引值,然后无论是交换值、加数组长度还是取负都是为了将缺省值和出现的值相区别。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值