LeetCode 283 Move Zeros

给定一个数组nums,写一个函数,将数组中所有的0挪到数组的末尾,而维持其他所有非0元素的相对位置。

举例:nums = [0,1,0,3,12],函数运行后的结果为[1,3,12,0,0]

程序初始:传入的是原始数组nums

class Solution:def moveZeroes(self, nums: List[int]) -> None:"""Do not return anything, modify nums in-place instead."""

首先先复习一下对于Py中循环的三种写法:

list = ['python','c++','java','c','go']
# 方法一:直接遍历列表
for i in list:print(i)print(list.index(i))# 方法二:用range的方式来遍历
for i in range(len(list)):print(list[i])print(i)# 方法三:使用enumerate的方法遍历
for i,val in enumerate(list):print(val)print(i)

直观的思路:

先扫描一遍数组,然后把数组中的非零元素全部拿出来存到一个non_list中,然后将list中和non_list相同index的值替换,再将list将没有填补的位置的元素全部赋成0。

程序实现:

  1. 我们先定义一个变量来存储所有的非零元素,接着扫描一遍列表,选出所有的非零元素:
nonZeroElements = [num for num in nums if(num)]
  1. 用非零元素替代之前list的元素:
# 方法一: 使用zip并行遍历
for i,j in zip(nonZeroElements,nums):nums[nums.index(j)] = i# 方法二:对长度小于nonlist的进行赋值
for i in range(len(nums)):if i < len(nonZeroElements):nums[i] = nonZeroElements[i]
  1. 最后将之后的元素赋为0
for i in range(len(nums)-len(nonZeroElements)):nums[len(nonZeroElements)+i] = 0
# 如果第二步用方法二赋值的话,可以直接加个else判断
class Solution:def moveZeroes(self, nums) -> None:"""Do not return anything, modify nums in-place instead."""nonZeroElements = [num for num in nums if(num)]for i,j in zip(nonZeroElements,nums):nums[nums.index(j)] = ifor i in range(len(nums)-len(nonZeroElements)):nums[len(nonZeroElements)+i] = 0return nums

复杂度分析

对于上述方法,循环执行了n次,故时间复杂度为O(n);因为也用了一个辅助空间数组,故空间复杂度为O(n);

优化

在上个算法中,最明显的是我们使用了一个新的list,即多了一个辐助的空间。那么我们可不可以不使用空间,而在原地直接对非0元素进行移动呢?

那么直接的思路是,少使用一个空间,多使用一个索引,使用另外一个索引k,让[0…k)中保存所有遍历过的k个非0元素。

操作的过程是:通过索引i遍历这个数组,当索引i遇到非零的元素时,将其指向的值赋值给k索引的位置,再将k索引后移一位,直到索引i遍历完所以值为止,最后将索引k位置及之后的元素都赋值为0即可。

class Solution:def moveZeroes(self, nums: List[int]) -> None:"""Do not return anything, modify nums in-place instead."""k = 0for i in range(len(nums)):if(nums[i]):nums[k] = nums[i]k += 1for i in range(len(nums)-k):nums[k] = 0k += 1return nums

复杂度分析,其循环执行了n次,故时间复杂度为O(n),而其没有用辐助空间数组,故其空间复杂度为O(1);

再优化

继续考虑上述方案,将值遍历完后,我们发现最后还要再对索引k位置及之后的元素再赋值为0,那么可不可以在循环的过程中就直接把0放在后面呢?

那么在刚才的思路上,我们只需把赋值给换成互换:遍历到第i个位置的元素后,我们不是把i位置的非零元素赋值给k,而是将i位置的非零元素和k位置的零元素互换,从而避免了最后的赋值。

class Solution:def moveZeroes(self, nums: List[int]) -> None:"""Do not return anything, modify nums in-place instead."""k = 0for i in range(len(nums)):if(nums[i]):nums[i],nums[k] = nums[k],nums[i]k += 1return nums

复杂度分析,其循环执行了n次,故时间复杂度为O(n),而其没有用辐助空间数组,故其空间复杂度为O(1);

对可能输入的数据来进行优化。很多优化本身,就是对特殊情况进行优化,如果输入的数组都是非零元素,那么对于上述情况,我们每次遍历都是对相同位置的元素进行了互换,因此我们还可以在其中加入判断条件,避免这种自身元素的互换。

class Solution:def moveZeroes(self, nums: List[int]) -> None:"""Do not return anything, modify nums in-place instead."""k = 0for i in range(len(nums)):if(nums[i]):if (i!=k):nums[i],nums[k] = nums[k],nums[i]k += 1else:                  k += 1return nums

LeetCode 27 Remove Element

问题

给定一个数组nums和一个数指val,将数组中所有等于val的元素删除,并返回剩余元素的个数。

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

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

例:nums = [3,2,2,3],val = 3; 返回2,且nums中前两个元素为2;

前言

peace,如果在面试中遇到了删除类问题,务必要问清楚细节,显示你考虑问题的全面,切忌上手就做!:

  1. 怎么来定义删除?从数组中直接去除,还是放在数组末尾?
  2. 删除过后剩余元素的排列是否要保证原有的顺序?
  3. 是否有空间复杂度的需求,用不用开辟新的额外空间?

题目的要求是空间复杂度为O(1),删除后剩余元素排列的顺序可以改变,即最后返回元素的个数即可。

1. 不使用remove

解决这个的思路和昨天的问题一样,如果想不额外再创造空间,那么首先想到的就是多个指针,然后通过指针索引来赋值或交换的操作,来达到目的。

那么我们依然使用双指针的情况,用[0…i)来表示删除过了指定值的数组。用j对list进行遍历:开始i和j指向元素相同,如果当前j指的值为指定的值,我们不用管它,继续遍历j,当j遍历到非指定元素值时,将这个元素赋值给i指向的元素,接着为了准备判断下一个指定值的元素,让i向后移动一位。
实现如下:

class Solution(object):def removeElement(self, nums, val):i = 0;for j in range(len(nums)):if (nums[j] != val):nums[i] = nums[j];i += 1return i;

因为遍历了n次,其时间复杂度为O(n),空间复杂度为O(1);

优化

正如上一篇中所说的,很多优化本身,就是对特殊情况进行优化,比如对可能输入的数据来进行优化。如果输入的数据是需要删除数据在首尾的情况:

比如:[1,2,3,5,4],要删除4;[4,1,2,3,5]要删除4;

使用上种方法,对于第一种情况,我们就做了很多次自身的复制;对于第二种情况更惨,我们让每个不是指定值得元素都进行了左移。那么如何避免多余的赋值,从而进行优化呢?

根据上一篇中的优化思路–为了避免最后对0再次遍历,采用了交换元素的思想,这样让0元素直接都在了末尾。

因为题中不用考虑顺序,那么在这里,我们可以使用首尾两个指针,同样令[0…i)来表示删除过了指定值的数组长度.(i表示末尾指针;j表示头指针索引)

使用j对list进行遍历,如果开始j指向的值是我们要的指定值,那直接将j与i进行交换,让指定的值排在最后,再让末尾指针i向前移动一位;如果不是指定的值,那么只需让头指针j向后移动即可。

这里要注意的时,当交换完毕后,我们仍然需要对当前交换完的值进行判断,所以这里最好使用while的写法,当i和j相同指向同一个元素时,循环跳出。

class Solution(object):def removeElement(self, nums, val):i = 0j = len(nums)while i < j:if (nums[i] == val):nums[i] = nums[j - 1]j -= 1else:i += 1return n

2. 使用remove

对于Py,可以取巧使用remove来进行删除,即遍历一遍list,哪个元素和提供的元素相同的话,则直接从list中移除。

复习下Py常用的三种删除的写法:

  1. remove删除

list.remove()进行删除,方法内传入指定要删除元素的值,当list中没有该元素时会报错,无返回值

nums = [1,3,2,0]
nums.remove(3)
[1,2,0]
  1. del删除

del是通过传入索引,根据位置来进行删除,无返回值

nums = [1,3,2,0]
del nums[3]
[1,3,2]
  1. pop删除

pop也是根据传入的索引进行删除,其返回值为被删除的元素。无参数值传入时默认删除最后一个

nums = [1,3,2,0]
nums.pop(1)
3
[1,2,0]

在Py的删除操作中,会有一个坑,我们按照思路进行删除,如下:

class Solution:def removeElement(self, nums: List[int], val: int) -> int:for i in nums:if i == val:nums.remove(val)return len(nums)

但是这样用for循环遍历在提交后会报错,错误样例为:

[0,1,2,2,3,0,4,2]
2
[0,1,3,0,4,2]

删除时,并没有把结尾的2删除干净,原因是因为我们用py对list进行遍历时,每次remove会改变list的长度,导致最后一个元素会没有遍历到,从而会对结尾需要删除的情况判断出错。因此对这种情况用while判断在不在list中即可。

class Solution:def removeElement(self, nums: List[int], val: int) -> int:while val in nums:nums.remove(val)return len(nums)

下一题:

LeetCode 26 Remove Duplicated from Sorted Array

LeetCode 80 Remove Duplicated from Sorted Array 2

LeetCode 26 Duplicated from Sorted Array

问题描述

给定一个排序数组,你需要在原地删除重复出现的元素,使得每个元素只出现一次,返回移除后数组的新长度。

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

例如给定nums=[1,1,2],结果应该会返回2,且nums的前两个元素为1和2;

PRE

显然,这依然是一个删除类的问题,对于删除类问题,依旧问三个问题

  1. 怎么来定义删除?从数组中直接去除,还是放在数组末尾?
  2. 删除过后剩余元素的排列是否要保证原有的顺序?
  3. 是否有空间复杂度的需求,用不用开辟新的额外空间?

一. 放在数组末尾

思路

不需要额外的空间,即空间复杂度为O(1),同样我们可以考虑使用双指针,因为是排序好的数组,故不存在相邻元素不想等,但和第三个位置上元素相等的情况,只需比较相邻元素相等即可。

假定[0…i)表示移除后的数组,i和j指向数组的先后位置:

如果两个位置元素相同,则将j继续向后移动,继续和i比较;如果两个位置元素不相同,则先将i向后移动一个位置,然后将j指向的值赋给i,再将j向后移动作比较;
(如果之前是相同的,那么此时i与i-1是相同的值,所以将不同的值替换;如果之前不同,那i和j都是指向同一个元素,同一个元素赋值也ok)

实现

class Solution:def removeDuplicates(self, nums: List[int]) -> int:i,j = 0,1while j < len(nums):if nums[i] == nums[j]:j += 1else:i += 1nums[i] = nums[j]j += 1return i+1

在刚开始写时,因为觉得py没法像c++一样可以在for循环内直接设定起始值,因而使用while麻烦了一些。实际上也可以使用使用py中的for循环:

py中的for循环的range写法中,有三种传参数的类型:

1个参数:stop

2个参数:start, stop

3个参数:start, stop, ste

因此可以使用for替代while,通过传入参数来控制开始和结束的遍历,这样可以少考虑变量的执行顺序:

实现

class Solution:def removeDuplicates(self, nums: List[int]) -> int:i = 0for j in range(1,len(nums)):if nums[i] != nums[j]:i += 1nums[i] = nums[j]return i+1

复杂度分析

遍历的次数为n,故时间复杂度为O(n);只对自身进行操作,故空间复杂度为O(1);

二. 从数组中删除

思路

上篇我们说过,在py中通过for循环来删除元素时,会出现索引错位的问题,上篇中使用的是while进行解决。在这里,我们可以仍然使用for循环,但是采用逆序遍历的方式来防止索引错位。

具体方法就是通过range方法,有len-1处start,0处为结尾,步长为-1,因为此时遍历的为索引,所以可以用pop或del方法对元素进行删除。

实现

class Solution:def removeDuplicates(self, nums: List[int]) -> int:for i in range(len(nums)-1, 0, -1):if nums[i] == nums[i-1]:nums.pop(i)return len(nums)

复杂度

遍历次数为n,但是py中list的方法pop(i)的时间复杂度为O(n),(如果是pop最后一个元素的话时间复杂度为O(1));

故总时间复杂度为O(n^2);只对自身进行操作,故空间复杂度为O(1);

LeetCode 80 Remove Duplicated from Sorted Array II

题目描述

给定一个排序数组,你需要在原地删除重复出现的元素,使得每个元素最多出现两次,返回移除后数组的新长度。

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

如nums = [1,1,1,2,2,3],结果返回5,且nums的前五个元素为:1,1,2,2,3

复杂度

这次题目和上道差不多,只是去重的规则改变了一下,变成了相同的值可以有两个。

ok,那考虑下,如何实现限制最多两次的重复出现。

同样,考虑问题时先不要去考虑边界,先从中间的值入手。如果开始遍历i时,当i-1与i+1的元素值不同,则表示至多有两个相同元素,此时可以继续后移比较,后移多少位呢?同样,刚才的判断只是证明了i-1与i+1不同,但是i和i+1还是可能相同,所以和刚才情况一样,只需后移动一位即可。

再考虑边界的情况,之前的写法我们默认可以有一个重复值,故i从0位置开始;此时可以有两个重复值,故i从1位置开始,另一个索引j在i的后一位,则从2位置开始。

实现

class Solution:def removeDuplicates(self, nums: List[int]) -> int:i = 1 for j in range(2,len(nums)):if nums[j] != nums[i-1]:i += 1nums[i] = nums[j]return i+1

复杂度

遍历了n次,时间复杂度为O(n);在自身上完成,空间复杂度为O(1);

LeetCode 75 Sort Colors

题目描述

给定一个包含红色、白色和蓝色,一共n个元素的数组,原地对它们进行排序,使得相同颜色的元素相邻,并按照红色、白色、蓝色顺序排列。

此题中,我们使用整数 0、1 和 2 分别表示红色、白色和蓝色。

注意:
不能使用代码库中的排序函数来解决这道题。

前言

n个元素的数组,且取值只有三种可能,故可能不是根据一般的排序来进行考虑,但是,如果面试上遇到了这个问题实在没有优化思路,首先应该考虑的是把问题先解决掉,再考虑用更好的方法,可以用普通排序算法先去解决。

首先我们想到的是快排,但是快排的时间复杂度平均为O(nlogn),能不能使用一种排序在O(n)内先解决问题?

一. 计数排序

思想

首先扫描一遍数组,统计0,1,2分别有多少个,再将0,1,2依次来放回数组,最后返回。

实现

class Solution:def sortColors(self, nums: List[int]) -> None:"""Do not return anything, modify nums in-place instead."""count = {0:0,1:0,2:0}for i in range(len(nums)):count[nums[i]] += 1index = 0for i in range(count[0]):nums[index] = 0index += 1for i in range(count[1]):nums[index] = 1index += 1for i in range(count[2]):nums[index] = 2index += 1return nums

时间复杂度

时间复杂度,遍历了n次,时间复杂度为O(n);

对于空间复杂度,开了一个字典,为O(k)级别,k为元素的取值范围;

二. 三路快排思想扩展

在计数排序中,第一次遍历统计元素频率,第二次遍历来统计数组,用到了两次遍历,那有没有只扫描数组一遍,来执行上述操作呢?

只扫描一遍就可以元素进行排序,这里容易想到的就是快排,因为快排的思想是:每次从当前考虑的数组中选择一个元素,以这个元素为基点,之后想办法把该元素放在它在排好序后应该所处的位置上。此时该元素就都具有了一个性质,在该元素之前的都是比这个元素小,之后都是比这个元素大,从而完成了对该元素的排序。

然而上述题目中的元素有三个值:0,1,2,而快排只是将其分为两拨,一拨小于指定值,一波大于指定值。

这里我们可以考虑下快排的扩展排序,回忆自己的基础知识,对快排的优化中,有随机初始化阈值,有二路快排,三路快排。而在三路快排中,其分组是将小于阈值,等于阈值,大于阈值分成了三波,而这里恰恰只有三个值,因此挪用三路快排的思想进行使用。

思路

因为只有三个元素,所以只对所有元素执行一次三路快排即可。

选取一个标定点,对于这个标定点,有很多个和这个标定点相等的元素值,所以将整个数组分为了三份,分别是小于V,等于V,和大于V。之后在小于V和大于V的地方,继续执行这种三路快排。

  1. 数组元素为0:

设置一个索引zero,索引从0到zero的数组元素一直保持为0;

  1. 数组元素为2
    设置一个索引叫做two,在数组中从two到n-1,一直保持为2;

  2. 数组元素为1
    当然我们也有一个遍历索引i,当索引i从zero+1到i-1的索引处,我们将其赋值为1;

同快排中考虑如何把阈值元素放在指定的排序位置上一样,当我们遍历到某一个元素e时,我们如何操纵这一元素e,使得数组一直维持上述的规则?

假设要遍历到的元素e有三种情况:

当e为1时,我们直接将其纳入到属于1的空间:zero+1到i-1,继续i++;

当e为2时,我们可以拿出two索引位置之前的元素,让其与2这个元素相交换,紧接着two指针–;

当e为0时,zero为值为0区间的最后一个元素,故将zero+1的1与这个e交换,再将zero++;

实现

class Solution:def sortColors(self, nums: List[int]) -> None:"""Do not return anything, modify nums in-place instead."""zero = -1   # nums[0...zeros] == 0two = len(nums) # nums[two...n-1] == 2i = 0while i < two:if nums[i] == 1:i += 1elif nums[i] == 2:two -= 1nums[i],nums[two] = nums[two],nums[i]else: # nums[i] == 0zero += 1nums[zero],nums[i] = nums[i],nums[zero]i += 1return nums

时间复杂度

因为整体只是遍历了一遍就完成了排序,故其时间复杂度为O(n);在本地完成操作,空间复杂度为O(1);

LeetCode 88 Merge Sorted Array

题目描述

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

说明:

初始化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]

核心:

空间复杂度为O(1)

初始化

前言

这个问题其实就是做归并排序中的归并那一步,ok,那么回忆下归并排序是怎么做的?

当要排序一个数组时,归并排序做的是,首先把数组分为一半,然后把左边的数组给排序,再把右边的数组给排序,之后再将他们归并起来(merge)。对左边的数组排序时,再分别将左边和右边的数组分成一半,然后对每一个部分先排序再归并。当分到某一个细度时,即一个元素时,只需对其归并即可。

归并的过程中,使用三个索引,来在数组内进行追踪,跟踪的位置,两个排好序的数组当前要考虑的元素,其中i,j指向的是当前正在指向的元素,k指向的是这两个元素最终应该放到的归并数组的位置,k的定义不表示归并结束后放置的位置,而表示下一个需要放的位置。

归并排序和快排的时间复杂度一样,都为O(nlogn),具体原因是,对于递归类函数的时间复杂度判断中,总体的时间复杂度为:递归的深度乘以每个递归函数的时间复杂度。

对于归并排序来说,其每次都是二分,递归的深度相当于是求2的几次方等于总长度,故其递归深度为log_2N,根据换底公式,其为log以任意数为底的n乘以一个常数,而其遍历n次,时间复杂度可表示为O(nlogn)。

对于空间复杂度,常规的归并排序需要一个存储空间的数组,故空间复杂度为O(n);

思路

对于这道题目而言,使用了归并排序的归并操作,其题目要求是将nums2合并到nums1中,使得nums1成为一个有序数组,即要求用**空间复杂度为O(1)**来进行解决。

在题目中有:

  1. m和n表示的是nums1和nums2中已经初始化的元素数量,而并非nums1和nums2的空间大小,也就是说nums1中空间足够大,但其中m个空间设置了该设的值,故我们可以将nums1的空间作为归并排序中的那个‘额外’的数组;
  2. 合并后的总的有效数目为m+n;
  3. 都是从0开始,所以nums1和nums2的最后一个元素的索引分别是m-1和n-1,合并后的nums1的最后一个元素的索引应该是m+n-1;

如何将其作为额外的数组呢,首先我们先考虑特殊情况,就是如果nums2的数组中的值都比nums1要小,那么nums2的值就都排入了那个额外的数组中,因此需要将nums1中需要留出前n个位置,而原来的位置放在原来的位置加n处。例如:nums1中有2个元素,m=2,nums2中有3个元素,n=3,那么需要把nums1中的第一个位置元素放在第四个位置上,第二个位置元素放在第五个位置上。于是有这样的初始化:

for i in range(m):nums1[i+n] = nums1[i]

但是这样顺序的做初始化会出现一个问题,就是当m>n时,顺序的这样迭代,会将原来的值给占掉,所以需要采用从后向前的方式来初始化,将索引为m-1的赋给索引为n+m-1,最后将索引为0的赋值给索引为n。初始化如下:

for i in range(n+m-1,n-1,-1):nums1[i] = nums1[i - n]

初始化完毕后,就按正常归并的思路走,设定一个索引k,用来指向每一个比较好的元素,再用i和j来表示左数组和右数组,分别对左数组和右数组的每个值进行比较,谁小就将值赋给k所在的位置,然后相应位置++,且k++,如果位置超出了自己数组的大小,就将另一数组中的所有值直接赋值即可。

class Solution:def merge(self, nums1: List[int], m: int, nums2: List[int], n: int) -> None:"""Do not return anything, modify nums1 in-place instead."""for i in range(n+m-1,n-1,-1):nums1[i] = nums1[i - n]i = n;  j = 0;  k = 0;  while k < n + m:if i >= n+m :nums1[k] = nums2[j]k += 1j += 1elif j >= n: nums1[k] = nums1[i]k += 1i += 1elif nums1[i] < nums2[j]:nums1[k] = nums1[i]k += 1i += 1else:nums1[k] = nums2[j]k += 1j += 1

归并排序

当要排序一个数组时,归并排序做的是,首先把数组分为一半,然后把左边的数组给排序,再把右边的数组给排序,之后再将他们归并起来。对左边的数组排序时,再分别将左边和右边的数组分成一半,然后对每一个部分先排序再归并。当分到某一个细度时,即一个元素时,只需对其归并即可。

多使用了存储的空间,使用了O(n)的空间复杂度;

把两个排好序的数组合并为一个排序好的数组;

使用三个索引,来在数组内进行追踪,跟踪的位置,两个排好序的数组当前要考虑的元素;

i,j指向的是当前正在指向的元素,k指向的是这两个元素最终应该放到的归并数组的位置,k的定义不表示归并结束后放置的位置,而表示下一个需要放的位置。

维持好算法中的变量的定义,在循环中一直满足;

LeetCode 215 Kth Largest Element in an Array

在一个整数序列中寻找第k大的元素:

如:给定数组[3,2,1,5,6,4], k = 2 , 结果为 5

首先最直观的想法就是排序,先排序好,再从排序好的数组中进行选择,好的排序算法的时间复杂度为O(nlogn)的算法,那能否可以在时间复杂度O(n)将问题解决呢?

完全可以,在这里,我们可以使用快排的思路,在O(n)的时间复杂度内将问题解决。

利用快排每一轮将选定的枢轴元素放在正确的排序位置上的性质,将正确位置的索引返回与k进行比较,如果k比其大,那么就递归在比返回位置大的那块寻找;如果k比其小,那么就递归在比返回值小的那块寻找。

因为避免了原有快排两边递归的情况,只选择了一侧进行递归,所以时间复杂度为O(n),而不用乘上logn。

在用上述的方法之前,我们先再来认识下快排。(在前面解决 LeetCode 75 问题时使用了三路快排的思想)

前言–快速排序

[外链图片转存失败(img-OCmnm6sC-1568598941725)(5675D93521A6447DBCAA3FA9B32676DB)]

初始–从一段开始

[外链图片转存失败(img-eGbEgrBv-1568598941726)(C1E1559ABF47464599C4B28CFB5326B2)]

适用输入值无重复元素

快速排序是每次从当前考虑的数组中选择一个元素,以这个元素为基点,之后想办法把该元素放在它在排好序后应该所处的位置上。

比如对数组:[4,6,2,3,1,5,7,8]排序,首先先要把4这个元素放在已经排序好的位置上,此时该元素就都具有了一个性质:即4之前的所有元素都是小于4的,4之后的所有元素都是大于4的。

接下来所做的事情,就是对当前排好序的元 素4之前和之后的部分,继续递归的进行上述过程,直至每个元素都排好序。

ok,那么问题就是如何将遍历的元素放在已经排序好的位置上,以及如何定义元素排好序了呢?

通常是选择第一个元素v当作基准点,索引记作l,之后遍历未访问的元素,在遍历的过程中,逐渐的整理,再使用j这个索引位置来记录小于v和大于v的分界点,当前访问的元素叫做i。

这样,[l+1,j]都是小于v,[j+1,i-1]都是大于v,那么i这个元素,该怎么变化才能使得整个数组仍保持这样的性质;

当i指的元素比v还要大,让i++,直接放在大于v的区间中;

当i指的元素比v小,则将j所指的后一个元素与i指的元素进行交换,再让j++,i++,进而考察下一个;

快速排序是不断的将数组一分为二,需要找到一个标定点,对标定点的左边和右边分别进行排序。

代码如下:

def partition(nums,l,r):v = nums[l]# nums[l+1...j] < v  nums[j+1...i) > vj = lfor i in range(l+1,r+1):if(nums[i] < v):j += 1nums[j], nums[i] = nums[i], nums[j]nums[l],nums[j] = nums[j],nums[l]return j
def quickSort(nums, l, r):if l >= r:returnp = partition(nums, l, r)quickSort(nums, l, p-1)quickSort(nums, p+1, r)return numsnums = [7,6,5,3]
lis = quickSort(nums,0,len(nums)-1)
print(nums)

快速排序也是对数组一分为二的过程,对于快排,找到一个标定点,将左右两个数组分别排序,快排可能分的是不平均的,
当整个数组近乎有序时,这样会造成时间复杂度过大,此时可以随机的选择一个元素作为枢纽值,而不必用第一个元素作为枢轴。

时间复杂度的期望值是nlogn。

优化–二路快排

二路快排是教科书及参考资料的标准写法,其将小于v和大于v放在数组的两端,把等于v的元素分散到了左右两部分,这样不会有等于v的元素不会集中于某一侧,而是将它们平分开来。

[外链图片转存失败(img-X1KkPk4h-1568598941727)(14A8596E181446EFAB05BE3F9DE48733)]

实现代码如下:

def partition(nums,l,r):v = nums[l]# arr[l+1,,,i)] <=v arr(j,,,r] >= vi = l + 1j = rwhile 1:while(i <= r and nums[i] < v):i += 1while(j >= l+1 and nums[j] > v):j -= 1if(i > j):breaknums[i],nums[j] = nums[j],nums[i]i += 1j -= 1nums[l],nums[j] = nums[j],nums[l]return j
def quickSort(nums, l, r):if l>= r:returnp = partition(nums, l, r)quickSort(nums,l,p-1)quickSort(nums,p+1,r)return numsnums = [7,6,5,3]
lis = quickSort(nums,0,len(nums)-1)
print(nums)

优化–三路快排

当对于输入值的情况有大量相等的元素时,交换元素同样需要消耗,那么就可以将整个数组氛围三部分:<v; =v; >v;这样,当递归交换时,就可以只对等于v之前与等于v之后的段落进行交换,如下:

[外链图片转存失败(img-lp0jpEMC-1568598941728)(286EA13F19154ABF80950D468140B72E)]

实现代码如下:

def partition(nums,l,r):if l >= r:returnv = nums[l]# arr[l+1,,,lt] < vlt = l# arr[gt,,,r] > vgt = r + 1# arr[lt+1,,,i) == vi = l + 1while(i < gt):if nums[i] < v:nums[i],nums[lt+1] = nums[lt+1],nums[i]lt += 1i += 1elif nums[i] > v:nums[i],nums[gt-1] = nums[gt-1],nums[i]gt -= 1else:i += 1nums[l],nums[lt] = nums[lt],nums[l]partition(nums,l,lt-1)partition(nums, gt, r)return numsarr = [1,3,2,4,1,6]
arr = partition(arr,0,len(arr)-1)
print(arr)

实现一:先排序再求k索引

先对指定数组nums进行快排,排序完后再取k的索引。如果是从小到大得排序,那么应该取得元素是-k;

class Solution:def partition(self,nums,l,r):if l >= r:return numsv = nums[l]# arr[l+1,,,lt] < vlt = l# arr[gt,,,r] > vgt = r + 1# arr[lt+1,,,i) == vi = l + 1while(i < gt):if nums[i] < v:nums[i],nums[lt+1] = nums[lt+1],nums[i]lt += 1i += 1elif nums[i] > v:nums[i],nums[gt-1] = nums[gt-1],nums[i]gt -= 1else:i += 1nums[l],nums[lt] = nums[lt],nums[l]self.partition(nums,l,lt-1)self.partition(nums, gt, r)return numsdef findKthLargest(self, nums, k) -> int:nums = self.partition(nums,0,len(nums)-1)return nums[-k]

实现二:只与k那部分递归

对返回的索引,与给定的k值进行比较,但在这里需要注意的问题有两个:

一是:原来返回的是数组的索引值,而k值是个数,故不能直接与k进行比较,需要将返回的索引值加1与k比较,此时递归的值也需要变化,之前p-1的,要变为p-2,而p+1的要变为p。

二是:如果是从小到大得排序,那么应该取得元素是len-k+1;
实现一:

class Solution:def partition(self, nums,l,r):v = nums[l]# nums[l+1...j] < v  nums[j+1...i) > vj = lfor i in range(l+1,r+1):if(nums[i] < v):j += 1nums[j], nums[i] = nums[i], nums[j]nums[l],nums[j] = nums[j],nums[l]return j+1def quickSort(self, nums, l, r, k):if l >= r:return nums[l]p = self.partition(nums, l, r)if p == k:return nums[p-1]elif k < p:return self.quickSort(nums, l, p-2, k)else:return self.quickSort(nums, p, r, k)def findKthLargest(self, nums, k) -> int:num = self.quickSort(nums,0,len(nums)-1,len(nums)-k+1)return num

实现二:

class Solution:def partition(self,nums,l,r):v = nums[l]# arr[l+1,,,i)] <=v arr(j,,,r] >= vi = l + 1j = rwhile 1:while(i <= r and nums[i] < v):i += 1while(j >= l+1 and nums[j] > v):j -= 1if(i > j):breaknums[i],nums[j] = nums[j],nums[i]i += 1j -= 1nums[l],nums[j] = nums[j],nums[l]return j + 1def quickSort(self, nums, l, r, k):if l >= r:return nums[l]p = self.partition(nums, l, r)if p == k:return nums[p-1]elif k < p:return self.quickSort(nums, l, p-2, k)else:return self.quickSort(nums, p, r, k)def findKthLargest(self, nums, k) -> int:num = self.quickSort(nums,0,len(nums)-1,len(nums)-k+1)return num

当然取最大k元素还有经典的堆排序,没有深入看,就先不解释这个方法了。

日常如果遇到这个问题时,其实大可不必这么麻烦,附一个py的简单实现:

class Solution:def findKthLargest(self, nums: List[int], k: int) -> int:nums.sort()return nums[-k]

LeetCode 167 Two Sum 2- Input array is sorted

题目描述

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

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

说明:

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

如:numbers = [2,7,11,15],target = 9

返回数字2,7得索引为1,2(索引从1开始计算)

这道题目比较简单,而且题目也最简化了情况:只有唯一解、不可以用相同的元素。但是因为面试中面试官可能不会带有这么多的条件,练习时也要发散的来思考一下,表明自己的细节完整:如果问题没有解怎么办?如果问题有多个解怎么办,按照什么顺序返回?如果可以单个元素重复的话,又该怎么考虑?

暴力搜索 O(n^2)

最直观解法,即暴力解法,使用双层遍历,i从0到len-1,j从i+1到len-1,这样经历了两层循环,故时间复杂度为:O(n^2)

(两层循环…就不写了)

显然,暴力解法没有充分利用原数组的性质–有序,谈到有序数组,首先必须想到什么?二分搜索!于是第二种思路就可以按照二分搜索对时间复杂度进行优化。

二分搜素思路优化:O(nlogn)

依次来遍历每一个元素i,对于每一个i,都在剩余的有序数组中,使用二分查找的思路,寻找target-nums[i],找到即返回,否则继续遍历。遍历为n,二分搜索为logn,总体的时间复杂度为:nlogn。

这里简单的复习一下二分搜素:

二分搜素

在一个有n个元素的有序数组中,寻找target的值,并且将找到的索引以int的形式返回。

在二分查找中,我们要在一个范围中不停的寻找target,然后这个范围逐渐的根据中间元素的不同来进行缩小。直到我们最终找到了这个target。

我们通过l,r两个边界来限定这个范围,在初始化时,首先的问题是就是这两个边界设定什么样的初值。

具体的边界设置为什么,不应该是靠猜测,而应该严格的限定清楚l和r两个变量的实际意义。

在这里我定义我是从[l,r]的闭区间的范围里去寻找target,

那么初始值设定为左边界0,右边界n-1。

有了这样的定义后,代码也要一直满足l和r相应的定义,下面开始定义循环,对循环的理解是,只要还有要查找值的话,那么我们就在这个循环中继续查找。

那么第二个问题就是应该写为l < r,还是l <= r?

这又是一个边界问题,遇到边界问题,一定要清楚自己最初的定义。定义是在[l,r]这个闭区间的范围内去寻找target,当l=r时,此时的区间l到r,就相当于是只有一个元素的区间,这个区间仍然是一个有效的区间,所以此时还应该查找下去。所以在while中的查找条件应该是l <= r。

代码如下:

def binarySearch(arr, target):l,r = 0,len(arr)-1while(l <= r):mid = int((l+r)/2)if(arr[mid] == target):return midelif(target > arr[mid]):# 此时带查找元素target,应该在mid元素右侧# 那么此时应该是mid还是mid+1?此时更新左边界,我们就要回到左边界的定义中去,arr[mid]不是我们寻找的target,那么target就应该在[mid+1,,,r]中,l = mid + 1else:# target在[l,,,mid-1]中r = mid - 1return -1

我记得上学期课的郭老师曾经在讲设计模式时说:记住变与不变,对于算法来说也是如此,要非常清晰的定义l和r两个变量的意义是什么,在下面的循环中,就去不断的去维护这个意义,保护这个循环不变量的意义。

当然也可以改写,如果初始r值为n,相应的对于r的意义是[l,r)寻找target;

def binarySearch(arr, target):l,r = 0,len(arr)while(l < r):mid = int((l+r)/2)if(arr[min] == target):return midelif(target > arr[mid]):# 此时带查找元素target,应该在mid元素右侧# 那么此时应该是mid还是mid+1?此时更新左边界,我们就要回到左边界的定义中去,arr[mid]不是我们寻找的target,那么target就应该在[mid+1,,,r)中,l = mid + 1else:# target在[l,,,mid)中r = midreturn -1

在对mid的求值时,我们使用的是(l+r)/2求中间值的方式,如果用C语言的话,两个int都足够大时,可能会出现整型溢出。虽然在py中不会出现整型溢出的问题,但是为了效率,我们也应该避免使用加法,优化求中间值元素的小套路就是:

mid = l + (r-l)/2;

对于二分搜索的时间复杂度,可以这样理解:第一次在n个元素中寻找,第二次在n/2个元素中寻找…直至最后在1个元素中寻找,这本质上就是在问–n经过几次"除以2"的操作后变成了1?那么自然答案是log以2为底,n的对数。那为什么不写log2n,而都表示为logn呢,这里就是用了数学中的对数换底公式,不管以几为底,都能表示为某一个为底的log再乘以一个常数,而常数忽略,故统一写为logn。

实现题目

class Solution:def twoSum(self, numbers, target):for i in range(len(numbers)-1):l = i + 1r = len(numbers) - 1while (l <= r):mid = int(l + (r - l) / 2)if numbers[mid] == target - numbers[i]:return [i + 1, mid + 1]elif target-numbers[i] > numbers[mid]:l = mid + 1else:r = mid - 1

对撞指针–O(n)

在前几个题目中,为了避免额外开辟新的数组空间,于是用了两个指针,来做交换删除之类的操作,同样这里,为了少一层循环,我们也可以考虑多用一个指针索引。

寻找两个索引,两个索引代表的数字和是target,因为数组有序,故其一定是一左一右存在,那么就从从最左侧选择i,从最右侧选择j:

  1. 如果第一个加上最后一个小于target,那么此时让i这个索引++,由于整个数组有序,故这个位置会比原来位置的值更大;
  2. 如果大于target,那么j–;由于有序,得到的结果会比上一次更小。

那么小套路–对撞指针,就是指的是用两个索引,向中间的方向不断前行,就能找到所给的答案。

因为不可以是重复元素,所以循环条件l < r

class Solution:def twoSum(self, numbers: List[int], target: int) -> List[int]:l,r = 0,len(numbers)-1while l < r:if numbers[l] + numbers[r] == target:return [l+1,r+1]elif numbers[l] + numbers[r] < target:l += 1else:r -= 1

只遍历一遍,时间复杂度为O(n),空间复杂度为O(1);

本科的时候在学浙大翁恺老师的程序设计课时,记得他说过:学程序题目就是要学其中的小套路。那么接下来的一篇,将把对撞指针相关的题目过一遍,时间会稍微有些长。

下一题:

LeetCode 125 Valid Palindrome

LeetCode 344 Reverse String

LeetCode 345 Reverse Vowels of a String

LeetCode 11 Container with most water

LeetCode 125 Valid Palindrome

题目描述

给定一个字符串,验证它是否是回文串,只考虑字母和数字字符,可以忽略字母的大小写。

说明:本题中,我们将空字符串定义为有效的回文串。

示例 1:

输入: “A man, a plan, a canal: Panama”

输出: true

示例 2:

输入: “race a car”

输出: false

思路

对于回文串问题,依然是对撞指针的思想,设前后两个指针,一个指针从头进行,一个指针从尾部进行。依次判断两个指针的字符是否相等,当两个指针相遇的时候循环停止跳出。

字符串问题中常要考虑的几个问题是:

1.空字符串怎么处理?

题目中已经将空字符串定义为有效的回文串,故不用特殊判断处理,默认循环完返回true就可以;

2.大小写问题

题目中忽略字母的大小写,而忽略字母大小写的套路做法是:将对应的字母统一转化为大写,再比较大写的字母是否相同。

3.跳过非法字符

Python中有字符串方法isalnum(),用来检测字符串是否由字母和数字组成,是的话返回true,如果没有库的话,可以用判断字符的ascii码来实现。

代码如下:

class Solution:def isPalindrome(self, s: str) -> bool:i,j = 0,len(s)-1while i < j:# 每次while都需要判断下i<jwhile i<j and not s[i].isalnum():i += 1while i<j and not s[j].isalnum():j -= 1if s[i].upper() != s[j].upper():return False# while循环中要改变循环变量i += 1j -= 1return True

时间复杂度

只遍历一遍数组,时间复杂度为:O(n);开辟的为常量空间,空间复杂度为O(1);

LeetCode 344 Reverse String

题目描述

编写一个函数,其作用是将输入的字符串反转过来。输入字符串以字符数组 char[] 的形式给出。

不要给另外的数组分配额外的空间,你必须原地修改输入数组、使用 O(1) 的额外空间解决这一问题。

思路代码

同样,使用对撞指针的思路,一个指针从前到后,一个指针从后向前,然后两个指针相互交换,当两个指针相同时跳出循环。

class Solution:def reverseString(self, s: List[str]) -> None:"""Do not return anything, modify s in-place instead."""i,j = 0,len(s)-1while i < j:s[i],s[j] = s[j],s[i]i += 1j -= 1return s

时间复杂度为O(n),空间复杂度为O(1);

题目中的小坑

对于Python,当然也可以使用list的切片来解决:

class Solution:def reverseString(self, s: List[str]) -> None:"""Do not return anything, modify s in-place instead."""s = s[::-1]return s

当然这样做时错的,因为题目的要求是原地修改输入数组,而这样做相当于是将改变的值赋给了局部变量s,而没有改变传入的s变量值。因为s是一个list,所以可以通过python s[0::] = s[::-1]来改变,为什么要求s是list,因为s如果是字符串的话,将不能原地修改,str是不可变对象。

基础知识点:

  1. 倒序遍历数组:s[::-1];
  2. 原地改变数组变量:s[0::] = s[::-1]
  3. py中可变对象:list,set,dict;不可变对象:字符串,元组
  4. 遇到需要交换元素时,需要考虑是类型的可变不可变;

LeetCode 345 Reverse Vowels of a String

题目描述

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

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

思路

同样,对撞指针,从前到后,从后到前,遇到每一个相同的元素进行交换。这时要注意,传入的为字符串,属于不可变对象,故要将字符串转化为list,交换后,再将list拼接为字符串。

基础知识点:

1.str->list: list(str)

2.list->str:’’.join(list)

代码

class Solution:def reverseVowels(self, s: str) -> str:i,j = 0,len(s)-1v = ['A','E','I','O','U'] lis_s = list(s)while i < j:while i<j and lis_s[i].upper() not in v:i += 1while i<j and lis_s[j].upper() not in v:j -= 1lis_s[i],lis_s[j] = lis_s[j],lis_s[i]i += 1j -= 1return ''.join(lis_s)

时间复杂度为O(n),空间复杂度为O(n)

LeetCode 11 Container with most water

给出一个非负整数数组a1,a2,a3…an,每一个整数表示一个竖立在坐标轴x位置的一堵高度为ai的’墙’,选择两堵墙,和x轴构成的容器可以容纳最多的水。

说明:你不能倾斜容器,且 n 的值至少为 2。

示例:

输入: [1,8,6,2,5,4,8,3,7]
输出: 49

[外链图片转存失败(img-YPbh3Fes-1568598941729)(A131D80D21544AC9860297F8581D3359)]

‘伪对撞指针’–O(n^2)

定义两个指针,一个从前向后,一个从后向前。矩形面积的长为两指针相减,矩形面积的高为两个指针比较后较小的值。首先固定i循环从0到len-1,再对j从len-1到i进行移动,比较每一次的面积值,最后返回。

class Solution:def maxArea(self, height: List[int]) -> int:maxarea = 0for i in range(len(height)):j = len(height)-1while i < j:maxarea = max(maxarea, min(height[i], height[j]) * (j - i));j -= 1return maxarea

时间复杂度为:n(n-1)/2,为O(n),空间复杂度只开辟了变量,故为O(1);

对撞指针精髓

这种做法,显然没有get到对撞指针的精髓,对撞指针并不是两次遍历,而是通过首尾指针在满足某种条件下,只移动一边的指针,最终指针重合跳出,达到O(n)的目的,那么可以在只移动一侧指针就能达到目的吗???

对撞指针–O(n)

矩形的面积由长和高决定,为了使面积最大化,我们需要考虑两条线段之间的区域更大,要么长度越长,要么是高度更高。但是由于面积取决于边长短的那一端,假设为m,所以要想得到比当前更大的面积,边长短的那一端必须舍弃,因为如果不舍弃,高最大也是m,而随着指针的移动宽会一直减小,因此面积只会越来越小。

说到这,可能还是有疑惑,这样移动会不会错过最优解的情况?

不会, 这相当于就是有一个数列{a0,a1,…an-1}一共n个(n>=2)。 取出任意两个值,然后result = min(ax,ay) * (y-x)

写出数列中任意两个元素的所有组合,一般的写法就是排列组合,但显然对于min(ax,ay) * (y-x)

在第一轮的筛选中,假设左边的a0是短元素(如果是右边也一样,这里以左边为例),显然(a0,an-1)的组合的值大于其他任何以a0为起始的组合的值,因为(a0,an-2)此时即使an-2的值大于an-1,但高度以低值为准,故高度还是a0,长度还减小了1。

故只能对值小的a0进行移动,此时虽然长度减小,但是期间可能出现高度大的值,有result大的可能性。

代码

class Solution:def maxArea(self, height: List[int]) -> int:i,j = 0,len(height)-1maxarea = 0while i < j:maxarea = max(maxarea, min(height[i], height[j])*(j - i))if height[i] > height[j]:j -= 1else:i += 1return maxarea

时间复杂度为O(n),空间复杂度为O(1)

下一题:

LeetCode 209 Minimum Size Subarray Sum

LeetCode 209 Minimum Size Subarray Sum

双索引的套路,除了对撞指针以外,即两个索引不再是通过首尾指针在满足某种条件下,只移动一边的指针,最终指针重合跳出,以达到只遍历一次O(n)的目的。

还有一类滑动窗口的套路,其两个索引不是遍历,而像表示的是一个窗口,让这个窗口不停的滑动,在这个数组中游走, 最终来找到我们希望求得的问题的解。

题目描述

给定一个整型数组和一个数字s,找到数组中最短的一个连续子数组,使得连续子数组的数字和sum>=s,返回这个最短的连续子数组的长度值。

例如:给定数组[2,3,1,2,4,3],s=7,答案为[4,3],返回2

分析

求解子数组问题时,需要先搞清楚以下几个问题:

  1. 连续不连续,如果是连续,有没有元素大小的情况?

题目中是连续的子数组,且不用考虑元素的大小情况。

  1. 没有解的情况需不需要考虑?

题目中没有解的情况,默认返回为0。怎么判断没有解,可以在开始设定初始值,最后判断如果初始值不变的话,就返回0.

  1. 返回的是什么?是子数组,还是子数组长度?

如果是只返回长度值,就不用考虑多组解的情况。如果是返回最短子数组,就要考虑多组解的情况–如果是要只返回某一个解,那么这个解有什么限定?如果是要返回多个解,那么这多个解按什么顺序来返回?

双指针O(n^2):

思路

遍历所有的连续子数组,计算其和sum,验证sum >= s,就拿这个当前sum的长度(长度由索引取得),与初始值进行比较,取最小的进行记录。

设置初始长度值时,之前我们比较最大时,将初始值设置为0或-1,因为最小也就是0,而在这里比较最小,反过来考虑,将初始值设置为len或len-1,因为题目中可能出现无解的情况,故考虑到最后一步判断,将初始值设置为len-1;

遍历全部连续子数组的小套路:首先遍历L从0到len-1,接着遍历R从L到len-1。

在遍历j时计算sum值,如果sum值>=s时,满足要求,此时比较求出最小的len值,接着break掉j的循环,因为再往后长度只会越大。

代码如下

class Solution:def minSubArrayLen(self, s: int, nums: List[int]) -> int:res = len(nums) + 1for l in range(len(nums)):sum = 0for r in range(l,len(nums)):sum += nums[r]if sum >= s:res = min(res, r-l+1)breakif res == len(nums) + 1:return 0return res

因为遍历两次,故时间复杂度为O(n^2);

空间复杂度为常量空间,故为O(1)

滑动窗口O(n)

思路

在上述思路中,其实包含了大量的重复计算,对于nums[i,j]到nums[i+1,j]的和,只需要减去nums[i]即可,但是刚刚的操作是又通过遍历一遍数组来实现。

那么如何避免这种重复操作呢?

滑动窗口的思想,就是开始定义了子数组[i,j],通过对这个子数组的sum全局值进行计算。

如果子数组的和还不到s,就往后多看一个数据,直到sum和大于s,就可以把其长度记录,此时如果再移动j,长度只会变大,故就可以从i这一端缩小这个数组。

这时连续子数组的和就会小一些。当sum和小于s时,就再去移动j,找到一个连续子数组,使其和大于s,如此循环。

整个上述过程,都保持着一个窗口,这个窗口的长度并不是固定的,但是是被i和j这两个索引所定义的,这个窗口不停的向前滑动,来寻找满足题意的连续子数组。

代码

class Solution:def minSubArrayLen(self, s: int, nums: List[int]) -> int:# [l,r]为滑动窗口,初始滑动窗口的值为0l,r = 0,-1sum = 0# 记录当前寻找到得最小长度,初始化为最大值,不可能取到这个值res = len(nums) + 1# 滑动窗口得左边界小于len,那么右边界也可以取值while l < len(nums):'''对于数组问题而言,一旦用方括号来取值得话,一定要注意数组越界的问题:需要保证r+1后还在取值范围中,因此在条件中需要进行限定'''if r+1 < len(nums) and sum < s:r += 1sum += nums[r]else:sum -= nums[l]l += 1if sum >= s:# 因为是连续闭空间,右边界减去左边界后,还要+1res = min(res, r-l+1)# 有可能遍历了整个数组都没有解的情况if res == len(nums) + 1:return 0return res

只遍历了一遍数组,故时间复杂度为O(n);

空间复杂度为常量空间,为O(1)

美团笔试题

题目描述

给定两个字符串,输出最短的包含全部字符的字符串

样例–>

输入:第一个字符串abac,第二个字符串 cab

输出:cabac

思路

题目中要求:最短的包含全部字符的字符串,怎么保证最短?其实也就是找公共子串最长。如何去找两字符串的最长公共子串,我这里用了滑动窗口的思路,规定[l,r)为最长公共子串,那么遍历其中任意一字符串,如果[l,r)如果在另一字符串里,r索引右移,看能否子串更长;如果没有在,就将l右移动。

但是这里值得注意的是,找到最长公共子串就结束了吗?

样例中,最长子串正好是第一个字符串的开头,以及第二个字符串的末尾,于是两个字符串得以拼接。

如果字符串是‘dabac’和‘cab’,即使它们的公共子串是’ab’,其拼接起来的字符串也不是以ab为连接的,而是通过公共字符子串‘c’来进行连接,形成‘dabacab’。那么这就需要在判断子串长度的这个条件下,另外加上判断条件,保证子串是两个字符串的前或者后,不是的话,即使其最长,也无法判定为连接要素。

最后就是如果公共子串如果既不是前也不是后,那么就直接返回两个字符串相加的结果,因为题目中没有说明,就默认为返回s1+s2

def minSubArrayLen(s1, s2):l,r = 0,0res = ''# 定义[l,,,r)为最长子串while l < len(s2):if r <= len(s2) and s2[l:r] in s1:sum = s2[l:r]r += 1else:l += 1'''第一个条件是为了确定最长子串;二三个条件是为了保证公共子串是首尾'''if len(sum) > len(res) and (s1.index(sum) == 0 or s1.index == len(s1) - len(sum)) and (s2.index(sum) == 0 or s2.index(sum)== len(s2) - len(sum)):print(res)res = sumprint(res)if s1.index(res) == 0 and s2.index(res) == len(s2)-len(res):return s2 + s1[len(res):]elif s2.index(res) == 0 and s1.index(res) == len(s1)-len(res):return s1 + s2[len(res):]else:return s1 + s2

LeetCode 3 Longest SubString Without Repeating Characters

问题描述

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

如:‘abcabcbb’,则结果为:‘abc’

如:‘bbbbb’,则结果为’b’

如:‘pwwkew’,则结果为’wke’

分析

仍然是字符串问题,求解时,需要先搞清楚:

  1. 子串需不需要连续

根据案例三,题目中是连续的子数组,不能有隔的字符

  1. 没有解的情况需不需要考虑?

题目中不存在没有解的情况,至少为1.

  1. 返回的是什么?是子数组,还是子数组长度?

如果是只返回长度值,就不用考虑多组解的情况。如果是返回最短子数组,就要考虑多组解的情况–如果是要只返回某一个解,那么这个解有什么限定?如果是要返回多个解,那么这多个解按什么顺序来返回?

题目中返回的是最长子串的长度

  1. 重复字母的大小写是否要考虑?

题目没有说明,暂不考虑

思路

同样,这也是一个典型的可以使用滑动窗口的问题,首先定义最长子串为s[i,j],在这个子串中,没有重复的字母,如果s[i,j]这个数组中没有重复字母,那么将j继续向后移动,看下一个字符是否和当前的字符串产生相同的字符存在。如果没有,就找到了一个更长的子串没有重复的字母,即s[i,j+1]。如果下一个字符和当前字符串数组中产生了重复的字母,那此时字符串就没办法再往后扩展了,就记录下此时字符串数组的长度,再将i向后移动,直到把这个重复的字符给刨除出去,此时j就可以包含刚刚重复的字母。

那么在这里,如何来判定下一个字符和当前的字符串中没有重复的字符呢?

用py实现的话当然很简单,看要判定的字符在不在当前的字符数组里,用in即可。(上一篇美团的题目中有体现)

如果不能用python中的in方法的话,怎么整呢?

可以设置一个数组,数组的k索引位置存的就是ASCII为k的字符在子串中出现的频率。可以用其面对下一个字符,查
找其在数组中出现的频率值为多少,如果为0就没有重复,继续移动;如果为1即产生了一个重复的字符。

代码实现

class Solution:def lengthOfLongestSubstring(self, s: str) -> int:l,r = 0,-1res = 0while l < len(s):if r+1 < len(s) and not s[r+1] in s[l:r+1]:r += 1else:l += 1res = max(res,r-l+1)return res

循环一遍,时间复杂度为O(n);空间复杂度为O(1)

LeetCode 438 Find All Anagrams in a String

题目描述

给定一个字符串s和一个非空字符串p,找到s中所有是p的字母异位词的子串,返回这些子串的起始索引。

字符串只包含小写英文字母,并且字符串s和 p的长度都不超过 20100

说明:

字母异位词指字母相同,但排列不同的字符串。
不考虑答案输出的顺序。

示例:

如:s = “cbaebabacd” p = “abc”,返回[0,6]

解释:
起始索引等于 0 的子串是 “cba”, 它是 “abc” 的字母异位词。
起始索引等于 6 的子串是 “bac”, 它是 “abc” 的字母异位词。

如:s = “abab” p = “ba” 返回[0,1,2 ]

解释:
起始索引等于 0 的子串是 “ab”, 它是 “ab” 的字母异位词。
起始索引等于 1 的子串是 “ba”, 它是 “ab” 的字母异位词。
起始索引等于 2 的子串是 “ab”, 它是 “ab” 的字母异位词。

包含字母一样,只是有可能顺序不同。

分析

仍然是字符串问题,求解时,需要先搞清楚:

  1. 子串需不需要连续

根据案例,s子串需要是p的字母异位词,故必须连续,不能有其他不属于p的字母。

  1. 没有解的情况需不需要考虑?

没有解返回的是空列表。

  1. 返回的是什么?是子数组,还是子数组长度?

返回子数组的开始索引,有多组解的情况不用考虑顺序。

  1. 重复字母的大小写是否要考虑?

题目中说明字符串只有英文小写字母,故不需要考虑。

思路

LeetCode第一阶段(一)【数组篇】相关推荐

  1. 黑马程序员C++学习笔记<第一阶段_基础篇>

    配套视频网址: 黑马程序员:http://yun.itheima.com/course/520.html?bili B站:https://www.bilibili.com/video/BV1et411 ...

  2. [原创].NET 业务框架开发实战之十 第一阶段总结,深入浅出,水到渠成(前篇)...

    .NET 业务框架开发实战之十 第一阶段总结,深入浅出,水到渠成(前篇) 前言:这个系列有段时间没有动了.主要是针对大家的反馈在修改代码.在修改的过程中,也有了一些新的体会,这里和大家分享一下,同时也 ...

  3. C++入门第一阶段——基础篇

    C++入门 如何创建C++程序 C++相关基础 变量 变量的意义 变量创建的语法 代码示例 常量 常量的意义 常量的定义方式 关键字 关键字的含义 sizeof 标志符的命名 什么是标志符 命名规则 ...

  4. [原创].NET 业务框架开发实战之十 第一阶段总结,深入浅出,水到渠成(后篇)...

    .NET 业务框架开发实战之十 第一阶段总结,深入浅出,水到渠成(后篇) 前言:接着上篇来. 系列文章链接: [原创].NET 分布式架构开发实战之一 故事起源 [原创].NET 分布式架构开发实战之 ...

  5. 第一阶段----MySQL学习----基础篇

    第一阶段----MySQL学习----基础篇 数据库-基础篇 MySql版本要求 基本语法 DQL语法 基本查询 条件查询 聚合函数 分组查询 排序查询 分页查询 语句练习 执行顺序 DCL-用户操作 ...

  6. PCIe扫盲系列博文连载目录篇(第一阶段)

    转载地址:http://blog.chinaaet.com/justlxy/p/5100053251 本文为PCIe扫盲系列博文连载目录篇(第一阶段),所谓第一阶段就是说后面还有第二阶段和第三阶段-- ...

  7. 迷你播放器--第一阶段(1)--检索媒体音乐并添加到List播放列表

    迷你播放器--第一阶段(1) 检索音乐并添加到List播放列表--媒体库的检索以及list列表使用 本文章为CSDN作者原创,转载请保留出处:http://blog.csdn.net/lrs0304/ ...

  8. 迷你播放器--第一阶段(5)--添加搜索功能--autoCompleteBox的使用

    迷你播放器--第一阶段(5) 添加搜索功能--autoCompleteBox的使用; 本文章为CSDN作者原创,转载请保留出处:http://blog.csdn.net/lrs0304/article ...

  9. 万人千题第一阶段报告【待继续总结】

    学习内容概况 目的:找编程和做题的手感 具体训练内容:万人千题第一阶段题库(思维导图),同时还有一些之前做过的题 练习后总结 具体细节之后补充为文字版,概况思维导图如下: 编程细节 位运算使用技巧 d ...

最新文章

  1. AVFoundation 文本转语音和音频录制 播放
  2. 大型系统OA--技术
  3. python小数据池,代码块的最详细、深入剖析
  4. 自动填充数据新增测试数据_用测试数据填充员工数据库
  5. python 保存文件 吃内存_孤荷凌寒自学python第三十七天python的文件与内存变量之间的序列化与反序列化...
  6. 设计模式——单例模式详解
  7. java sqlexec_java 执行Sql文件
  8. sql server远程主机强迫关闭了一个_交换机远程端口镜像
  9. Linux下SCP使用技巧
  10. (15)HTML面试题集锦
  11. 在英特尔架构服务器上构建基于矢量包处理(VPP)的快速网络协议栈
  12. 【转】关于Eclipse创建Android项目时,会多出一个appcompat_v7的问题
  13. ddos php源码,ddos PHP版_php
  14. html倒计时星期日,功能齐全的jQuery倒计时插件
  15. 【乐理入门】——音符与五线谱(1)
  16. ubuntu清理cache
  17. Ubuntu及window的配置 java变量和快捷键
  18. 如何在您的香港主机帐户上注册多个域名
  19. 基于STM32的照片查看器课程报告
  20. Datawhale-数据分析-泰坦尼克-第一单元

热门文章

  1. 从零搭建阿里云服务器(图文详解)
  2. 面向对象分析与设计 实验一
  3. 全志H616方案香橙派Zero2开发板Linux系统设置静态 IP 地址的方法
  4. promethues+alertmanager+grafana监控docker容器和报警—基于手动配置和文件自动发现—详细文档
  5. 1N4007、M7、A7整流二极管,有什么区别?
  6. 1046: 奇数的乘积 Python
  7. php.ino,北京大学POMINO v2 NO2卫星产品发布
  8. CAS: 1260119-01-4, NO2-UIO-66, UIO-66-NO2
  9. 如何编辑维基百科词条?WIKI词条编辑技巧
  10. java性能调优jstat使用方法