数组的遍历查找

题目:

前三题都可以用集合和来求。【sum(set(nums))*N-sum(nums)】

1、数组中别的数都出现2次,只有一个数出现1次,找出该数。

2、数组中别的数都出现3次,只有一个数出现1次,找出该数。

3、数组中别的数都出现N次,只有一个数出现1次,找出该数。

4、数组中别的数都出现2次,只有两个数只出现1次,找出这两个数。

5.1、数组中1到1000遍历(顺序打乱),有两个数缺失,求出这两个数。

  5.2、数组中未出现的最小正整数【左右变量遍历】

6、数组中别的数都出现1次,只有一个数出现2次,找出该数。

  【n个正整数,每个数值不超过n-1,有一个重复的数,找出那个重复的数】

7、n个[0,n)的数,求每个数出现次数(不能开辟额外空间)【排序】

8、数组去重

9、在数组中找到出现次数大于N/K的数【删除不同的数】

10、在一个有序数组中查找一个数,怎么最快【二分查找】

11、在行列都排好序的矩阵中找数

12、奇数下标都是奇数或偶数下标都是偶数【even、odd变量】

13、在数组中找到一个局部最小的位置

14、边界都是1的最大正方形大小【遍历+动态规划】

15、不包含本位置值的累乘数组【左右额外空间遍历】

16、求最短通路值【宽度优先】

17、二维数组的转置(每行数组的长度一样和不一样)

18.股票最大利润

解题:

1、(3)第1题相当于第3题N为偶数。解法思路:数组为nums,则数组中所有数异或,最后的值为所求。

复制代码
def findNum(nums):
  if not nums:
    return -1
i=1 res=nums[0]
   while i<len(nums): res^=nums[i]
     i += 1 return res
复制代码

2、(3)第2题相当于第3题N为奇数。解法思路:所有数的二进制位相加 mod 3 ,所有模3值不为0的位汇总的数即为所求。

复制代码
def findNum(nums):
    if not nums:
        return -1
    i=0
    res=0
    bitSum=[0]*4  #这里是4是因为若设32位,但无论怎么算都只需4位
    while i<4:
        j=0
        while j<len(nums):
            bitSum[i]+=(nums[j]>>i)&1
       j+=1 res|=(bitSum[i]%3)<<i i+=1 return res
复制代码

更简单的解法:一个数用来表示出现一次的位,也就是说这个数的为1的位就表示这个位上面的数出现过了一次,比如0x10001,就表示bit[0]和bit[4]就是出现过了一次的位。然后再用一个数表示出现过了两次的位,再用一个数表示出现过了3次的位。只要这个位出现过了3次,我就把这个位拿清除掉,这样剩余的最后出现过一次的位的这个数就是我们要找的数了。

复制代码
def findNum(nums):
    ones=twos=threes=0
    for i in range(len(nums)):
            twos |= ones & A[i]
            ones ^= A[i]
            threes = ones & twos
            ones &= ~threes
            twos &= ~threes
    return ones
复制代码

第3题:所有数的二进制位相加 mod n ,所有模n值不为0的位汇总的数即为所求。

def findNum(nums,n):
    if not nums:
        return -1
    arr = [0] * 32
    for i in range(len(nums)):
        binnum = nums[i]
        j = 0
        #将十进制转成二进制
        while binnum:
            binnum = binnum >> j
            arr[j] += binnum & 1
            j += 1
    res = 0
    #记录%n有余数的数
    for i in range(len(arr)):
        tmp = (arr[i] % n)&1
        res += tmp << i
    return int(res)

nums = [1000,1000,1000,2,3,4,4,4,3,3]
n = 3
print(findNum(nums,n))
import math
def findNum(nums,n):
    if not nums:
        return -1
    arr = [0] * 32
    for i in range(len(nums)):
        binnum = nums[i]
        j = 0
        while binnum:
            binnum , remainder = divmod(binnum,2) #divmod将商和余数结合起来
            if remainder == 1:
                arr[j] += 1
            j += 1
    res = 0
    for i in range(len(arr)):
        if arr[i] % n == 1:
            res += math.pow(2,i)
    return int(res)

nums = [5,5,5,2,3,4,4,4,3,3]
n = 3
print(findNum(nums,n))

第4题:先将数组所有数字异或,最后结果就是两个出现一次的数字相互异或的结果,再将这两个数分别分在两个数组中进行异或。如链接:

https://blog.csdn.net/morewindows/article/details/8214003

    设题目中这两个只出现1次的数字分别为A和B,如果能将A,B分开到二个数组中,那显然符合“异或”解法的关键点了。因此这个题目的关键点就是将A,B分开到二个数组中。由于A,B肯定是不相等的,因此在二进制上必定有一位是不同的。根据这一位是0还是1可以将A,B分开到A组和B组。而这个数组中其它数字要么就属于A组,要么就属于B组。再对A组和B组分别执行“异或”解法就可以得到A,B了。而要判断A,B在哪一位上不相同,只要根据A异或B的结果就可以知道了,这个结果在二进制上为1的位都说明A,B在这一位上是不相同的。

      比如int a[] = {1, 1, 3, 5, 2, 2}

      整个数组异或的结果为3^5即 0x0011 ^ 0x0101 = 0x0110

      对0x0110,第1位(由低向高,从0开始)就是1。因此整个数组根据第1位是0还是1分成两组。

      a[0] =1  0x0001  第一组

      a[1] =1  0x0001  第一组

      a[2] =3  0x0011  第二组

      a[3] =5  0x0101  第一组

      a[4] =2  0x0010  第二组

      a[5] =2  0x0010  第二组

      第一组有{1, 1, 5},第二组有{3, 2, 3},明显对这二组分别执行“异或”解法就可以得到5和3了。

复制代码
def findNum(nums):
    if not nums:
        return -1
    temp=0
    ##先所有数异或得到A^B的值,A和B为只出现一次的数
    for item in nums:
        temp^=item
    #找到A和B不同的第一位,A^B的第一个1位。该位设为j位
    j=0
    while j<32:
        if (temp>>j)&1==1:
            break
        j+=1
    A=[]
    B=[]
    #将A、B分到按第j位不同分到不同组A和B
    for num in nums:
        if (num>>j)&1==1:
            A.append(num)
        else:
            B.append(num)
    ##分到的两个组全部都异或
    resA=0
    resB=0
    for itemA in A:
        resA^=itemA
    for itemB in B:
        resB^=itemB
    return resA,resB
nums=[1,1,3,5,2,2]
print(findNum(nums))
复制代码

5.1题:数组中1到1000遍历(顺序打乱),有两个数缺失,求出这两个数。

  • 思路1:list(set(range(1,1001))-set(nums)),即两个集合相减
  • 思路2:排序,然后比较【时间O(nlogn),空间O(1)】
  • 思路3:采用字典,将数组的元素存到key中,从1-n查询时若有哪两个数缺失则返回。【空间换时间】

5.2题:数组中未出现的最小正整数【左右变量遍历】

给定一个无序整型数组arr,找到数组中未出现的最小正整数。

举例:

arr = 【-1,2,3,4】。返回1

arr = 【1,2,3,4】。返回5.

思路:时间O(N),空间O(1)

分析:

假如arr有序,则:

(1)  arr为整数1,2,3…N,N+2,N+3……的一个随机排列,那个未出现的最小正整数就是N+1。

【设置left变量表示已经有序的N个数 arr [0:N-1] 】

(2)  arr中有小于1或者大于N或者重复的数出现(我们称之为“不合法”的数),则未出现的最小正整数一定在1到N中间(因为数组一共只有N个数,如果出现不合法的数,则出现的1到N之间的数的个数一定小于N,故一定有没有出现的数)。

【设置right变量,初始N+2, 表示还可以有序的arr [ N:N+2 ]】

【比如:发现 arr[N](即N+2)合法但位置不理想,将N+2换到它理想的位置,然后将N+3换到该位置 arr[ N ],继续比较N+3是不是合法且理想,发现并不合法,合法区间right-1变成 arr[ N:N+1 ] 】

做法:

(1)  先设置两个变量L,R。初始值:L=0,R=len(arr)

    • L表示已经从1到L已经出现(左边界),L的初值为0。
    • 如果一个数字过大(不合法),就会被扔掉,用R表示这个右边界,即大于R的数会被扔掉。R的初值为N,表示从1到R的元素都不会被扔掉,大于R的就会被扔掉。但是这个R的值是变化的,如果L+1到R中有一个元素不合法,那么R--,因为最多只能放下R-1个合法数。

也就是说,1到L上的数已经出现,[L+1,R]区间上的数未出现但可能会出现

(2)遍历数组:

1》当 L 的位置值等于L+1,表示得到想要的,故L ++

2》三种不合法情况:都需R--

    • 小于左边的数:当 L 的位置值< L 时,表示 L 位置的值已经存在,所以这个数组不会存在到 R 的值了 so R--
    • 大于右边的数:当 L 的位置值> R 时,表示 L 的位置已经超过 R ,所以数组不会存在到 R 的值了, so R--
    • 左边区域重复的数:当 L 的位置值和 (L 值的位置-1)的的位置值相等时,所有数组不会存在到 R 的值了,so R--    【即L位置的值已经存在左区域已经出现的数中了,两个一样的值,比如 [1,2,3,2,4] ,第二个2已经重复了[1,2,3]中的2了 】

3》合法但位置不理想:else,将 arr【L】 交换到理想位置,理想位置的值换到 L 位置来,继续判断 L 位置的值。

代码:

def missNum(arr):
    if not arr:
        return 1
    left , right = 0 , len(arr)
    while left < right:
        if arr[left] == left + 1:
            left += 1
        elif arr[left] <= left or arr[left] > right or arr[left] == arr[arr[left]-1]:
            right -= 1
            arr[left] = arr[right]  #right-=1后合法区域不包括arr【right】了,将arr[right]换到left位置继续判断
        else: #arr[left]理想位置为arr[left]-1,将arr[arr[left]-1]换到left位置继续判断
            arr[left] , arr[arr[left] - 1] = arr[arr[left]-1] , arr[left]
    return left + 1
    
arr = [1,3,-2,4,5]
missNum(arr)

第6题:数组中别的数都出现1次,只有一个数出现2次,找出该数。

  • 思路1:【空间换时间】,采用字典,将数组的元素存到key中,value对应数量,若value=2则返回该key值。
  • 思路2:【累加求和法,可能溢出】,sum(nums) - sum([1……n])即所求
  • 思路3:【异或法】nums数与 [1……n] n个数异或,异或结果为所求
  • 思路4:【数据映射法】全部变为负数,最后一个为正数的即所求。【如arr = [2,3,4,3,1],令arr[0] = -2,指向下标为2,即arr[2] = -4, arr[4] = -1, arr[1] = -3,arr[3] = -3,arr[3] = 3变为正数,即为所求。】如果元素不合理时,存在数组越界问题
  • 思路5:【环形相遇法】将数组连成链表,如果有重复值则会形成环形链表,只需要求入环点即可。
  • 思路6:排序,找相邻的数。

9、在数组中找到出现次数大于N/K的数

思路:找到出现最多的那个数且次数超过一半。方法是遍历过程中成对删除不同的数,一个数如果出现次数超过一半,剩下的数必是该数,否则可能是也可能不是。如:

【4,3,7,4,4,4,5】,设置一个cand和time变量。开始,

开始,cand = 4,time= 1,

遇到下一个数3,两者不同,time - 1 = 0

遇到下一个数7,因为time=0,所以cand重置为=7,time=1

遇到下一个数4,cand和4不同,所以time - 1 =0

遇到下一个数4,time = 0,cand重置为4,time= 1

遇到下一个数4,cand和4相同,time + 1 = 2

遇到下一个数5,cand和5不同,time - 1 = 1

故:剩下的数cand为4。

然后从头开始遍历,判断4是否出现超过一半【因为如果没超过一半也可能cand为4,如 [ 4,3,4,5]】

代码:

复制代码
def printHalfMajor(arr):
    if arr == None or len(arr) == 0:
        print("No such number!")
        return False
    cand = 0
    time = 0
    for i in range(len(arr)):
        if time == 0:
            cand = arr[i]
            time += 1
        elif cand == arr[i]:
            time += 1
        else:
            time -= 1
    time = 0
    for i in range(len(arr)):
        if arr[i] == cand:
            time += 1
    if time > len(arr)//2:
        return True
    else:
        return False
arr=[4,3,4,3,4,6,5,4]
printHalfMajor(arr)
复制代码

进阶问题思路:一次删掉K个不同的数,多次删除,直到剩下的数种类不足K。若一个数出现次数超过N/K,该数最后一定会剩下。

一个map记录K个不同的数,一旦map大小==k-1,且下一个arr[i]不在map中,就可以开始删除map中K个不同值。

代码:

复制代码
def printHalfMajor(arr,k):
    if arr == None or len(arr) <= k:
        print("No such number!")
        return False
    #重点:每次删除K种不同的值【采用字典存储K种不同的值】
    map = {}
    for i in range(len(arr)):
        if arr[i] in map:
            map[arr[i]] += 1
        else:
            if len(map) == k-1:
                for key in list(map):
                    map[key] -= 1
                    if map[key] == 0:
                        del map[key]
            else:
                map[arr[i]] = 1
    #查询map剩余的数在原数组中的数量,【如[1,1,3,3,4,4,4,4,2,2]中最后map会剩下{4:2,2:2}】
    time = 0
    flag = False
    for key in map:
        for i in range(len(arr)):
            if arr[i] == key:
                time += 1
        if time > len(arr)//k:
            flag = True
            print(key)
        time = 0
    return flag
                
arr=[4,3,4,3,4,6,5,4]
K = 3
printHalfMajor(arr,K)
复制代码

10、在行列都排好序的矩阵中找数

思路:

11、奇数下标都是奇数或偶数下标都是偶数

给定一个长度不小于2的数组arr,实现一个函数调整arr,要么让所有的偶数下标都是偶数,要么让所有的奇数下标都是奇数。

思路:时间O(N),空间O(1)

设置三个变量,even下标、odd下标、end下标

只要arr【end】 是偶数,就将arr【end】和arr【even】交换,even+=2.

同样,arr【end】 是奇数,就将arr【end】和arr【odd】交换,odd+=2.

代码:

复制代码
def test(arr):
    if not arr or len(arr) <= 1:
        return arr
    even = 0
    odd = 1
    end = len(arr) - 1
    while even < len(arr) and odd < len(arr):
        if arr[end] % 2 == 0:
            arr[end] , arr[even] = arr[even] , arr[end]
            even += 2
        elif arr[end] % 2 == 1:
            arr[end] , arr[odd] = arr[odd] , arr[end]
            odd += 2
    return arr
arr = [1,8,3,2,4,6]
test(arr)
复制代码

12、边界都是1的最大正方形大小

给定一个N*M的矩阵matrix, 在这个矩阵中, 只有0和1两种值, 返回边框全是1的最大正方
形的边长长度。
例如:
0 1 1 1 1
0 1 0 0 1
0 1 0 0 1
0 1 1 1 1
0 1 0 1 1
其中, 边框全是1的最大正方形的大小为4*4, 所以返回4

思路1:时间O(N4),枚举所有正方形,判断边框是否都为1

1.矩阵中一共有N*N个位置。O(N2)

2.对每一个位置都可以成为边长为N~1的正方形左上角。比如,对于(0,0)位置,依次检查是否是边长为5的正方形的左上角,然后检查边长为4、3等。O(N)

3.如何检查一个位置是否可以成为边长为N的正方形的左上角?遍历这个边长为N的正方形边界看是否只由1组成,也就是走过四个边的长度(4N)。O(N)

总的时间复杂度:O(N2)*O(N)*O(N)=O(N4)

思路2:时间复杂度为O(N3),以空间换时间的做法。

采用预处理矩阵的方法,同样也是枚举所有的正方形,但是判断该正方形是否符合规则是,是O(1)的时间复杂度,所以当M=N时,这是O(N^3)时间复杂度。 

用与原矩阵同样大小的两个矩阵,一个为right,一个为down,

right[i][j]的值表示从位置(i,j)向右出发有多少个连续的1。

down[i][j]的值表示从位置(i,j)向下出发有多少个连续的1。

right和down的计算过程:从右到左,从下到上依次填好两个矩阵。

      • 从矩阵的右下角(n-1,n-1)位置开始计算,如果matrix[n-1][n-1]=1,那么,right[n-1][n-1]=1,down[n-1][n-1]=1,否则都等于0。
      • 从右下角向上计算,即在matrix最后一列上计算,位置就表示为(i,n-1)。对right来说,最后一列的右边没有内容,所以,如果matrix[i][n-1]=1,             right[i][n-1]=1并且down[i][n-1]=down[i+1][n-1]+1,否则right[i][n-1]=0并且down[i][n-1]=0。
      • 从右下角向左计算,即在matrix最后一行上计算,位置就表示为(n-1,j)。对down来说,最后一行的下边没有内容,所以,如果matrix[n-1][j]=1,           down[n-1][j]=1并且right[n-1][j]=down[n-1][j+1]+1,否则right[n-1][j]=0并且down[n-1][j]=0。
      • 剩下的位置都是既有右,又有下,假设位置(i,j):
      • if matrix[i][j]=1, then right[i][j+1]=right[i][j]+1,down[i][j]=down[i+1][j]+1.
      • if matrix[i][j]=0,then right[i][j]=0,down[i][j]=0.

13、不包含本位置值的累乘数组

  给定一个整型数组arr,返回不包含本位置值的累乘数组。
  例如,arr = [2, 3, 4, 1],返回[12, 8, 24, 6],即除自己以外,其他位置上的累乘。

【要求】

      1. 时间复杂度O(N)
      2. 除需要返回的结果数组外,额外空间复杂度O(1)。

不用除法思路:

分别使用辅助两个数组left和right,其中left表示数组从左到右的累乘结果(即left[i] = arr[0…i]的累乘);相反,right表示数组从右到左的累乘结果。那么对于结果数组res,res[i] = left[i-1] * right[i+1]。
  实际上,并不需要额外声明两个辅助数组。可以复用结果数组res,即先将res当辅助数组用,再把res调整为结果数组即可。具体实现见如下代码:

 

def product2(arr):
    if arr == None or len(arr) < 2:
        return
    res = [0 for i in range(len(arr))]
    res[0] = arr[0]
    for i in range(1, len(res)):
        res[i] = res[i-1] * arr[i]
    tmp = 1
    for i in range(len(arr)-1, 0, -1):
        res[i] = res[i-1] * tmp
        tmp *= arr[i]
    res[0] = tmp
    return res

arr = [2,3,1,4]
product2(arr)

 

 17、二维数组的转置

题目:

代码:

    

def merge(arr,n):
    if not arr:
        return arr
    res = []  
    num = [0] * len(arr)
    count = len(arr)
    while count:
        for i in range(len(arr)):
            m = len(arr[i])
            if num[i] + n <= m:
                res.extend(arr[i][num[i]:num[i]+n])
                num[i] += n
            elif num[i]+n > m and arr[i][num[i]:]:
                res.extend(arr[i][num[i]:])
                num[i] += n
                count -= 1
    return res
arr = [[2,5,6,7,9,5,7],[1,7,4,3,4]]
n = 3
merge(arr,n)

  长度一样的转置:【采用解压*】list(zip(*matrix))

 18. 股票最大利润

 思路1:用后一天减去前天得到隔天的利润,然后将该题目转化为求最大子序列和的问题。

思路2:当天的价格减去今天以前的股票最小值

 

 代码:

arr = [7,1,4,3,1]
min_P = 2**32
max_P = -min_P
if not arr:
    print('0')
else:
    for i in range(len(arr)):
        min_P = min(min_P,arr[i])
        max_P = max(max_P,arr[i]-min_P)
    print(max_P)

    

    

原文地址:https://www.cnblogs.com/Lee-yl/p/10469654.html