算法&数据结构(三):数组

leetcode:448. 找到所有数组中消失的数字

问题描述:给定一个范围在  1 ≤ a[i] ≤ n ( n = 数组大小 ) 的 整型数组,数组中的元素一些出现了两次,另一些只出现一次。找到所有在 [1, n] 范围之间没有出现在数组中的数字。不使用额外空间且时间复杂度为O(n)

解法:把元素的值作为下标,将下标对应的值取负,然后返回正数的下标

时间复杂度: O(n)

class Solution(object):
    def findDisappearedNumbers(self, nums):
        """
        :type nums: List[int]
        :rtype: List[int]
        """
        # 把元素的值作为下标,将下标对应的值取负,然后返回正数的下标
        # 元素可能已经为负数 abs(i)
        # 元素重复出现改变对应值 -abs(nums[abs(i) - 1])
        for i in nums:
            nums[abs(i) - 1] = -abs(nums[abs(i) - 1])
        return [k + 1 for k, v in enumerate(nums) if v > 0]
        

leetcode:581. 最短无序连续子数组

问题描述:给定一个整数数组,你需要寻找一个连续的子数组,如果对这个子数组进行升序排序,那么整个数组都会变为升序排序。

你找到的子数组应是最短的,请输出它的长度。

解法:将数组排序然后对比原数组不同的元素,最后一个不同元素的下标 - 第一个不同元素的下标 + 1

class Solution(object):
    def findUnsortedSubarray(self, nums):
        """
        :type nums: List[int]
        :rtype: int
        """
        sort_nums = sorted(nums)
        res = [i for i, (v1, v2) in enumerate(zip(nums, sort_nums)) if v1 != v2]
        if res == []:
            return 0
        else:
            return max(res) - min(res) + 1

剑指offer:二维数组中的查找

问题描述:一个二维数组中(每个一维数组的长度相同),每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。

解法:从第一行的最后一列数字,即右上角数字开始。若是数字大于目标,则往左移动(列);若是数字小于目标,则往下移动(行);直到找到目标,或者范围为空。

时间复杂度:0 (k)

# -*- coding:utf-8 -*-
class Solution:
    # array 二维列表
    def Find(self, target, array):
        # i 行 j 列
        i = 0
        j = len(array[0]) - 1
        while i < len(array) and j >= 0:
            if array[i][j] == target:
                return True
            elif array[i][j] > target:
                j -= 1
            else:
                i += 1
        return False

leetcode:34. 在排序数组中查找元素的第一个和最后一个位置

问题描述:给定一个按照升序排列的整数数组 nums,和一个目标值 target。找出给定目标值在数组中的开始位置和结束位置。

你的算法时间复杂度必须是 O(log n) 级别。

解法:二分查找到数字之后,左右移动指针查找开始和结束位置

时间复杂度: O(log n)

class Solution:
    # 二分查找
    def find_med(self, nums, target):
        l = 0
        r = len(nums) - 1
        while l <= r:
            mid = (l + r) // 2
            if nums[mid] == target:
                return mid
            elif nums[mid] < target:
                l = mid + 1
            elif nums[mid] > target:
                r = mid - 1
                
    # 左右相同值查找
    def searchRange(self, nums: List[int], target: int) -> List[int]:
        res = []
        if nums == [] or target is None:
            return [-1, -1]
       
        t = self.find_med(nums, target)
        
        if t is None:
            return [-1, -1]
        else:
            pre = t
            while pre > 0 and nums[pre-1] == target:
                pre -= 1
            last = t
            while last < len(nums) - 1 and nums[last+1] == target:
                last += 1
            # 输出开始位置和结束位置
            return [pre, last]

剑指offer:斐波那契数列

问题描述:斐波那契数列,现在要求输入一个整数n,请你输出斐波那契数列的第n项(从0开始,第0项为0)

解法:递归解法会重复计算节点,用循环的方式从底至上计算避免了重复计算

时间复杂度:O(n)

class Solution:
    def Fibonacci(self, n):
        # write code here
        res = [0, 1]
        if n < 2:
            return res[n]
        n_sub_1 = 1
        n_sub_2 = 0
        f_n = 0
        for i in range(n-1):
            f_n = n_sub_1 + n_sub_2
            n_sub_2 = n_sub_1
            n_sub_1 = f_n
        return f_n

剑指offer:跳台阶

问题描述:一只青蛙一次可以跳上1级台阶,也可以跳上2级。求该青蛙跳上一个n级的台阶总共有多少种跳法(先后次序不同算不同的结果)

解法:斐波那契数列的变形

时间复杂度:O(n)

class Solution:
    def jumpFloor(self, number):
        if number < 1:
            return 0
        elif number == 1:
            return 1
        elif number == 2:
            return 2
        else:
            fib1 = 1
            fib2 = 2
            for i in range(number-2):
                fibn = fib1 + fib2
                fib1 = fib2
                fib2 = fibn
            return fibn

剑指offer:变态跳台阶

问题描述:一只青蛙一次可以跳上1级台阶,也可以跳上2级……它也可以跳上n级。求该青蛙跳上一个n级的台阶总共有多少种跳法。

解法: 

n个台阶:一次到位 + f(n-1) + f(n-2) + f(n-3) +...+ f(2) + f(1)

n-1个台阶:一次到位 + f(n-2) + f(n-3) +...+ f(2) + f(1)

2个台阶:一次到位 + f(1)

1个台阶:f(1) = 1

根据数学归纳法,当n=1时,显然成立;假设当n=k时(把式中n换成k,写出来)成立

证明 f(n) =2^{n-1}

时间复杂度:O(1)

class Solution:
    def jumpFloorII(self, number):
        return 2**(number-1)

剑指offer:矩形覆盖

问题描述:我们可以用2*1的小矩形横着或者竖着去覆盖更大的矩形。请问用n个2*1的小矩形无重叠地覆盖一个2*n的大矩形,总共有多少种方法?

解法:竖着放,f(n-1);横着放,f(n-2)

时间复杂度:O(n)

class Solution:
    def rectCover(self, number):
        if number < 1:
            return 0
        elif number == 1:
            return 1
        elif number == 2:
            return 2
        else:
            fib1 = 1
            fib2 = 2
            for i in range(number-2):
                fibn = fib1 + fib2
                fib1 = fib2
                fib2 = fibn
            return fibn

剑指offer:二进制中1的个数

问题描述:输入一个整数,输出该数二进制表示中1的个数。其中负数用补码表示。

解法:

如果输入是负数,右移时最高位会设为1,从此陷入死循环。

常规解法——标记为1,不断左移标记1,与输入做与运算判断1的个数,但是这个解法需要考虑二进制的所有位数。

最佳解法——输入减1,与原输入做与运算,会把输入最右边的一个1变成0

时间复杂度:O(k)

# -*- coding:utf-8 -*-
class Solution:
    def NumberOf1(self, n):
        count = 0
        if n < 0:
            # 获得负数的补码
            # python中负数(十进制)输出的是原码二进制加上负号,负数(十六进制)输出的是对应的补码,因此需要手动将其和十六进制数0xfffffffd进行按位与操作,得到十六进制数
            n = n & 0xffffffff
        while n:
            n = (n - 1) & n
            count += 1
        return count

剑指offer:调整数组顺序使奇数位于偶数前面

问题描述:输入一个整数数组,实现一个函数来调整该数组中数字的顺序,使得所有的奇数位于数组的前半部分,所有的偶数位于数组的后半部分,并保证奇数和奇数,偶数和偶数之间的相对位置不变。

解法:

时间复杂度:O(n)

1.如果不要求奇数和偶数的相对位置不变,只要能奇数在前,偶数在后。解法如下:

两个指针,一个在前一个在后,分别指向奇数偶数,然后交换位置

def reOrderArray(array):
    if array is None or len(array) <= 0:
        return None

    p_head = 0
    p_tail = len(array) - 1
    while p_head < p_tail:
        # 找到偶数
        while p_head < p_tail and array[p_head] % 2 != 0:
            p_head += 1
        # 找到奇数
        while p_head < p_tail and array[p_tail] % 2 == 0:
            p_tail -= 1

        if p_head < p_tail:
            array[p_head], array[p_tail] = array[p_tail], array[p_head]
            p_head += 1
            p_tail -= 1
    return array

2.如果要求奇数和偶数的相对位置不变,解法如下:

方法一:构造奇数和偶数数列,分别存放元素,最后返回合并的数组

class Solution:
    def reOrderArray(self, array):
        # 奇数
        odd_num = []
        # 偶数
        even_num = []
        for i in array:
            if i % 2 == 0:
                even_num.append(i)
            else:
                odd_num.append(i)
        return odd_num + even_num

方法二:从前往后遍历,指针指向可以存放的奇数位置,如果遇到奇数就往前冒泡直到奇数标志

class Solution:
    def reOrderArray(self, array):
        # 指向可以存放奇数的位置
        odd_flag = 0
        for i in range(len(array)):
            # 找到奇数
            if array[i] % 2 != 0:
                for j in range(i, odd_flag, -1):
                    array[j], array[j-1] = array[j-1], array[j]
                odd_flag += 1
        return array

剑指offer:顺时针打印矩阵

问题描述:输入一个矩阵,按照从外向里以顺时针的顺序依次打印出每一个数字,例如,如果输入如下4 X 4矩阵: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 则依次打印出数字1,2,3,4,8,12,16,15,14,13,9,5,6,7,11,10.

解法:一次打印矩阵的一个圈,假设矩阵为5x5,那么初始点分别是(0,0) (1,1) (2,2),那么循环打印的条件为rows > start * 2 且columns > start * 2,需要考虑只有一行、只有一列、只有一行一列的情况。

时间复杂度:O(n)

class Solution:
    # matrix类型为二维列表,需要返回列表
    def printMatrix(self, matrix):
        if matrix is None:
            return None
        res = []
        start = 0
        # 行 row
        rows = len(matrix)
        # 列 column
        columns = len(matrix[0])
        while rows > start * 2 and columns > start * 2:
            # 打印圈
            endX = rows - 1 - start
            endY = columns - 1 - start
            # 从左往右,行不变;打印一圈至少走一步
            for i in range(start, columns-start):
                res.append(matrix[start][i])

            # 从上往下,列不变;如果只有一行,不需要第二步,需要终止行>起始行
            if start < endX:
                for i in range(start+1, rows-start):
                    res.append(matrix[i][endY])

            # 从右往左,行不变;第三步需要圈内至少两行两列,需要终止行>起始行,终止列>起始列
            if start < endX and start < endY:
                for i in range(endY-1, start-1, -1):
                    res.append(matrix[endX][i])

            # 从下往上,列不变;第四步需要圈内至少三行两列,需要终止行>起始行+1,终止列>起始列
            if start < endX-1 and start < endY:
                for i in range(endX-1, start, -1):
                    res.append(matrix[i][start])

            # 更新初始位置
            start += 1
        return res

leetcode:78. 子集

问题描述:给定一组不含重复元素的整数数组 nums,返回该数组所有可能的子集(幂集)。

解法:动态规划,每新增一个元素则在原有基础上+[i]

f([1]) = [[], [1]]

f([1,2]) = [[], [1], [2], [1,2]] = f([1]) + [[]+[2], [1]+[2]]

时间复杂度:O(n)

class Solution:
    def subsets(self, nums: List[int]) -> List[List[int]]:
        # 初始化为二维数组
        dp = [[]]
        for i in range(len(nums)):
            # print(dp)
            # print([nums[i]])
            # print([_ + [nums[i]] for _ in dp])
            # print('###')
            dp = dp + [_ + [nums[i]] for _ in dp]
        return dp

面试问题:循环单词

如果一个单词通过循环右移获得的单词,我们称这些单词都为一种循环单词。例如:picture和turepic就是属于同一种循环单词。现在给出n个单词,需要统计这n个单词中有多少种循环单词。

解法:难点在于判断两个单词是不是循环单词、如何把几种单词记做一种。创建循环单词数组,如果单词不在循环数组里,则加入它的所有右移循环单词;如果单词在循环数组里,标记为False。将标记为True的元素放入结果数组,返回结果数组的长度。

def circle_word(lines):
    if len(lines) == 0:
        print(0)
    list_word = []
    res = []

    for line in lines:
        flag = True
        if line in list_word:
            flag = False
        else:
            for j in range(len(line) - 1, -1, -1):
                # 单词右移,最后一位移到第一位
                list_word.append(line[j:] + line[:j])
        if flag:
            res.append(line)
    print(len(res))


if __name__ == '__main__':

    a = ['word', 'ordw','picture','turepic','turepic','icturep','ps','sp']
    circle_word(a)

面试问题:操作序列

小易有一个长度为n的整数序列,a_1,...,a_n。然后考虑在一个空序列b上进行n次以下操作:
1、将a_i放入b序列的末尾
2、逆置b序列
小易需要你计算输出操作n次之后的b序列。

解法:找规律。

首先根据题意写出数组直接转置的程序:

line = [1,2,3,4,5,6,7,8,9,10]
res = []
for i in line:
    res.append(str(i))
    res = res[::-1]
print(res)

结果为:['10', '8', '6', '4', '2', '1', '3', '5', '7', '9']

发现从从最后一个数字开始往前,跳一格;然后从前往后,也是跳一格,但是奇序列和偶序列有点区别,在从前向后输出时,奇序列从第二个数开始,偶序列从第一个开始

import sys

n = str(sys.stdin.readline().strip())
line = sys.stdin.readline().strip()
line = list(map(int, line.split()))

res = []
# 从最后一个数开始,从后向前输出,每次都跳过一个
for i in range(len(line)-1, -1, -2):
    res.append(str(line[i]))
# 偶序列从第一个开始
if len(line) % 2 == 0:
    for i in range(0, len(line)-1, 2):
        res.append(str(line[i]))
# 奇序列从第二个数开始
else:
    for i in range(1, len(line)-1, 2):
        res.append(str(line[i]))
print(' '.join(res))

leetcode:665. 非递减数列

问题描述:

解法:

case1:如果没有一处n[i]>n[i+1],满足条件
case2:如果只有一处n[i]>n[i+1],那么只要通过修改n[i]或者n[i+1]来达到n[i-1]<=n[i]<=n[i+1]<=n[i+2]满足条件
        只有一处n[i]>n[i+1],当n[i-1]<=n[i+1] || n[i]<=n[i+2]成立时即满足条件,当n[i-1]>n[i+1] && n[i]>[i+2]成立时即不满足条件
case3:如果n[i]>n[i+1]有两处及其以上,必然不满足条件

时间复杂度:O(N)

class Solution:
    def checkPossibility(self, nums: List[int]) -> bool:
        flag = 0
        for i in range(len(nums)-1):
            if nums[i] > nums[i+1]:
                # 当修改n[i]时 需要保证n[i-1]<=n[i+1]即满足条件
                # 当修改n[i+1]时 需要保证n[i]<=n[i+2]即满足条件
                if 1 <= i < len(nums)-2 and nums[i+1] < nums[i-1] and nums[i+2] < nums[i]:
                    return False
                flag += 1
        # 如果只有0、1处n[i]>n[i+1]
        if flag <= 1:
            return True
        else:
            return False

剑指offer:数组中出现次数超过一半的数字

问题描述:数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字。例如输入一个长度为9的数组{1,2,3,2,2,2,5,4,2}。由于数字2在数组中出现了5次,超过数组长度的一半,因此输出2。如果不存在则输出0。

解法一:哈希表,判断最大键值是否大于长度的一半

# -*- coding:utf-8 -*-
class Solution:
    def MoreThanHalfNum_Solution(self, numbers):
        dict1 = {}
        for i in range(len(numbers)):
            dict1[numbers[i]] = dict1.get(numbers[i], 0) + 1
        # 最大值对应的键 max(dict1, key=dict1.get)
        if dict1[max(dict1, key=dict1.get)] > len(numbers) / 2.0:
            return max(dict1, key=dict1.get)
        else:
            return 0

解法二:保存两个值:数字,出现次数。

当下一个数字与保存的数字不同,次数减去一;

当下一个数字与保存的数字相同,次数加上一;

如果次数为零,保存下个数字,并且次数设置为一。

# -*- coding:utf-8 -*-
class Solution:
    def MoreThanHalfNum_Solution(self, numbers):
        res = numbers[0]
        count = 1
        for i in range(1, len(numbers)):
            # 当下一个数字与保存的数字相同,次数加上一
            if numbers[i] == res:
                count += 1
            # 当下一个数字与保存的数字不同,次数减去一
            else:
                count -= 1
            # 如果次数为零,保存下个数字,并且次数设置为一
            if count == 0:
                count = 1
                res = numbers[i]
        count = 0
        for i in range(len(numbers)):
            if numbers[i] == res:
                count += 1
        # 检查一下找到的元素是否个数多于长度的一半
        if count * 2 > len(numbers):
            return res
        else:
            return 0

leetcode:892. 三维形体的表面积

问题描述:在 N * N 的网格上,我们放置一些 1 * 1 * 1  的立方体。每个值 v = grid[i][j] 表示 v 个正方体叠放在对应单元格 (i, j) 上。

请你返回最终形体的表面积。

解法:顶部和底部不会被遮挡,且当v = grid[i][j] > 0,有且只有2个面。四周东南西北,取决于grid[i][j]的个数,再减去上下左右遮挡它的个数(注意最小为0,不能加上负数)

时间复杂度:O(N*N)

class Solution:
    def surfaceArea(self, grid: List[List[int]]) -> int:
        # 顶部和底部不会被遮挡,且当v = grid[i][j] > 0,有且只有2个面
        # 四周东南西北,取决于grid[i][j]的个数,再减去上下左右遮挡它的个数
        res = 0
        # 行
        n = len(grid)
        # 列
        m = len(grid[0])
        for i in range(n):
            for j in range(m):
                if grid[i][j] > 0:
                    res += 2
                    for r, c in ((i-1, j), (i, j-1), (i+1, j), (i, j+1)):
                        if 0 <= r < n and 0 <= c < m:
                            # 四周其中一个方向,需要扣除周围遮挡的个数
                            # grid[i][j] - grid[r][c] 可能为负值,不能加上负数
                            res += max(grid[i][j] - grid[r][c], 0)
                        else:
                            res += grid[i][j]
        return res
     

剑指offer:连续子数组的最大和

问题描述:给一个数组,返回它的最大连续子序列的和

解法:初始化最大和为负无穷,因为如果元素为全为负数,则最大和也是负数,不能初始化为0;

如果和<=0,(当前元素+和)<=当前元素,则和更新为当前元素;

如果和>0,则返回当前元素+和。

时间复杂度:

# -*- coding:utf-8 -*-
class Solution:
    def FindGreatestSumOfSubArray(self, array):
        if not array:
            return 0
        cur_sum = 0
        # 如果元素为全为负数,则最大和也是负数,因为不能初始化为0
        res = float("-inf")
        # 和必须大于0,否则当前元素加上和比当前元素还要小
        for i in range(len(array)):
            if cur_sum <= 0:
                cur_sum = array[i]
            else:
                cur_sum += array[i]
            res = max(cur_sum, res)
        return res

剑指offer:从1到n整数中1出现的次数

问题描述:从1 到 n 中1出现的次数

解法:例如输入213,分别考虑个位、十位、百位为1的情况:

个位为1,个位为最高位有1种 [1] ;改变当前位的前面几位数有21种 [11,21,31,41,...,201,211] ;

十位为1,十位为最高位有4种 [10,11,12,13] ;改变当前位的前面几位数有20种 [110,111,...,119,210,211,212,213,014,....,019] ;

( [10-213] 分解为两部分:[10-13] [14-213] 来处理)

百位为1,百位为最高位有100种 [100-199] ;改变当前位的前面几位数有0种;

时间复杂度:

# -*- coding:utf-8 -*-

def NumberOf1Between1AndN_Solution(n):
    res = 0
    cur = n
    count = 1

    while cur:
        # 求余数,例如123->3
        final_n = cur % 10
        # 整除,例如123->12
        cur = cur / 10
        # res: 当前位的前面几位,保持原始数的位数不变
        res += cur * count
        # print('当前位的前面几位: ', cur * count)
        # res: 当前位作为最高位
        # 如果当前位上是1,则n % count会得到后面几位数的值+1,例如123->24,百位为1,因为只能取到100-123,24个值
        if final_n == 1:
            res += n % count + 1
            print('当前位作为最高位: ', n % count + 1)
        # 如果当前位上不为1,例如213->100,百位为2,因为100-199都可以取到,100个值
        elif final_n > 1:
            res += count
            print('当前位作为最高位: ', count)
        # 从个位开始依次往上
        count *= 10
        # print('res = ', res)
    return res

print(NumberOf1Between1AndN_Solution(213))

剑指offer:把数组排成最小的数

问题描述:输入一个正整数数组,把数组里所有数字拼接起来排成一个数,打印能拼接出的所有数字中最小的一个。例如输入数组{3,32,321},则打印出这三个数字能排成的最小数字为321323。

解法: python2 自定义排序函数。直接拼接数字,可能导致数值溢出,这是一个隐形的大数问题,需要把数字转换成字符串。然后需要定义一种比较两个数的规则,即把数字m和n拼接为mn,nm,只需要按照字符串大小的比较。

# -*- coding:utf-8 -*-
class Solution:
    def PrintMinNumber(self, numbers):
        if not numbers:
            return ''
        # 直接拼接数字,可能导致数值溢出,这是一个隐形的大数问题,需要把数字转换成字符串
        str_list = map(str, numbers)
        # 把数字m和n拼接为mn,nm,只需要按照字符串大小的比较
        str_list.sort(lambda x, y: cmp(x+y, y+x))
        return ''.join(str_list)

leetcode:179. 最大数

问题描述:给定一组非负整数,重新排列它们的顺序使之组成一个最大的整数

解法:python3 自定义排序函数。定义一种比较两个数的规则,即把数字m和n拼接为mn,nm,只需要按照字符串大小的比较。

class Solution:
    def largestNumber(self, nums: List[int]) -> str:
        from functools import cmp_to_key
        if not nums:
            return ''
        nums = map(str, nums)
        key = cmp_to_key(lambda x, y: int(y + x) - int(x + y))
        # lstrip() 方法: 截掉字符串左边的空格或指定字符  0012->12
        res = ''.join(sorted(nums, key=key)).lstrip('0')
        # 000->''
        return res or '0'

剑指offer:丑数

问题描述:把只包含质因子2、3和5的数称作丑数(Ugly Number)。例如6、8都是丑数,但14不是,因为它包含质因子7。 习惯上我们把1当做是第一个丑数。求按从小到大的顺序的第N个丑数。

解法:每一个丑数都是前面的丑数乘以2、3、5得到的,假设最大丑数为m,那么将丑数数组的每一个元素乘以2,取第一个大于m的值为m2,依次得到m3,m5。下一个丑数为m2 m3 m5 三个数中最小的一个。

时间复杂度:O(k)

# -*- coding:utf-8 -*-
class Solution:
    def GetUglyNumber_Solution(self, index):
        if index <= 0:
            return 0
        m2 = 1
        m3 = 1
        m5 = 1
        # 最小的丑数是1
        num = [1]
        m = 1
        if index == 1:
            return 1
        while len(num) < index:
            # m2 m3 m5 三个数中最小的一个
            m = min(m2 * 2, m3 * 3, m5 * 5)
            while m2 * 2 <= m:
                # 防止重复数组的出现
                if m2 * 2 not in num:
                    num.append(m2 * 2)
                m2 = num[num.index(m2) + 1]

            while m3 * 3 <= m:
                if m3 * 3 not in num:
                    num.append(m3 * 3)
                m3 = num[num.index(m3) + 1]

            while m5 * 5 <= m:
                if m5 * 5 not in num:
                    num.append(m5 * 5)
                m5 = num[num.index(m5) + 1]
        return m

剑指offer:数字在排序数组中出现的次数

问题描述:统计一个数字在排序数组中出现的次数

解法:二分查找找到数组的第一个k和最后一个k的下标。还有一种简单做法是用python的count方法。

时间复杂度:O(logn)

# -*- coding:utf-8 -*-
# 思路一
class Solution:
    def GetNumberOfK(self, data, k):
        return data.count(k) 


# 思路二
class Solution:
    def GetNumberOfK(self, data, k):
        def get_first(data, k):
            start, end = 0, len(data)-1
            while start <= end:
                mid = (end + start) / 2
                if data[mid] == k:
                    # 找到这个元素后判断是否为第一个 k
                    if mid == 0 or (mid > 0 and data[mid-1] != k):
                        return mid
                    else:
                        end = mid - 1
                elif data[mid] < k:
                    start = mid + 1
                else:
                    end = mid - 1
            # 找不到这个元素
            return -1
        
        def get_last(data, k):
            start, end = 0, len(data) - 1
            while start <= end:
                mid = (end + start) / 2
                if data[mid] == k:
                    # 找到这个元素后判断是否为最后一个 k
                    if mid == len(data)-1 or (mid < len(data)-1 and data[mid+1] != k):
                        return mid
                    else:
                        start = mid + 1
                elif data[mid] < k:
                    start = mid + 1
                else:
                    end = mid - 1
            # 找不到这个元素
            return -1
        # 如果数组不为空
        if len(data) > 0:
            first = get_first(data, k)
            last = get_last(data, k)
            # 如果能找到元素
            if first > -1 and last > -1:
                return last - first + 1
        # 其余情况返回 0
        return 0

剑指offer:数组中只出现一次的数字

问题描述:一个整型数组里除了两个数字之外,其他的数字都出现了两次。请写程序找出这两个只出现一次的数字。

解法:0异或任何数不变,任何数与自己异或为0。a⊕b⊕a=b。异或满足加法结合律和交换律。

首先数组中所有元素依次异或,因为相同的元素异或得到0,所以最终的答案就等于2个出现一次的元素a^b的值。找到a^b的二进制表示中从右边数第一个为1的位,假如是第k位。而a,b两个数在第k位上是不同的,一个为0,一个为1 。然后将第k位是1的分成一组,第k位是0的分成一组,如果2个元素相同,那么他们第k位相同会进入同一组。在分组里面异或每一个元素,得到结果。

时间复杂度:O(n)

# -*- coding:utf-8 -*-
# 思路一:哈希表
class Solution:
    # 返回[a,b] 其中ab是出现一次的两个数字
    def FindNumsAppearOnce(self, array):
        dict1 = {}
        for i in array:
            dict1[i] = dict1.get(i, 0) + 1
        ans = []
        for k, v in dict1.items():
            if v == 1:
                ans.append(k)
        return ans

# 思路二:count方法
class Solution:
    # 返回[a,b] 其中ab是出现一次的两个数字
    def FindNumsAppearOnce(self, array):
        return [i for i in array if array.count(i)==1]

# 思路三:异或
class Solution:
    # 返回[a,b] 其中ab是出现一次的两个数字
    def FindNumsAppearOnce(self, array):
        # 找到二进制数的从右边数第一个为1的位置
        def FindFirstOne(num):
            pos = 0
            while num & 1 == 0:
                num = num >> 1
                pos += 1
            return pos

        # 判断从右边数的index是不是1
        def IsBit1(num, indexBit):
            num = num >> indexBit
            return num & 1

        if not array or len(array) < 2:
            return
        res = 0
        # 数组中所有元素依次异或
        for i in array:
            res ^= i
        # 从0开始的位置
        pos_index = FindFirstOne(res)
        num1 = 0
        num2 = 0
        # 分为两种情况异或
        for i in range(len(array)):
            if IsBit1(array[i], pos_index):
                num1 ^= array[i]
            else:
                num2 ^= array[i]
        return num1, num2

剑指offer:和为S的连续正数序列

问题描述:输出所有和为S的连续正数序列(至少两个数)。序列内按照从小至大的顺序,序列间按照开始数字从小到大的顺序

解法:用small和big代表序列的最小值和最大值,从1和2开始初始化。如果序列和大于s,则去掉较小值,增大small;如果小于s,则增大big。遍历在small到 (s + 1) / 2 为止。

# -*- coding:utf-8 -*-
class Solution:
    def FindContinuousSequence(self, tsum):
        ans = []
        small = 1
        big = 2
        cur_sum = small + big
        mid = (tsum + 1) / 2
        # 如果tsum为11,则small的最大值不能超过6,因为6+7>11
        # 遍历在small到 (s + 1) / 2 为止
        while small < mid:
            if cur_sum == tsum and small < big:
                ans.append([i for i in range(small, big+1)])
            # 如果序列和大于s,则去掉较小值,增大small
            while cur_sum > tsum and small < big:
                cur_sum -= small
                small += 1
                if cur_sum == tsum and small < big:
                    ans.append([i for i in range(small, big + 1)])
            # 如果小于s,则增大big
            big += 1
            cur_sum += big
        return ans

剑指offer:和为S的两个数字

问题描述:输入一个递增排序的数组和一个数字S,在数组中查找两个数,使得他们的和正好是S,如果有多对数字的和等于S,输出两个数的乘积最小的。

解法:从两边到中间,两个指针遍历。两边的乘积是最小的。

时间复杂度:O(n)

# -*- coding:utf-8 -*-
class Solution:
    def FindNumbersWithSum(self, array, tsum):
        if not array or not tsum:
            return []
        l = 0
        r = len(array) - 1
        res = float('inf')
        ans = []
        while l < r:
            cur_sum = array[l] + array[r]
            # 找到第一个就是乘积最小的,直接退出
            if cur_sum == tsum:
                ans = (array[l], array[r])
                break
            elif cur_sum > tsum:
                r -= 1
            else:
                l += 1
        return ans

剑指offer:扑克牌的顺子

问题描述:从扑克牌随机抽取5张,判断是不是顺子。大小王可以看成任何数字,并且A看作1,J为11,Q为12,K为13。如果牌能组成顺子就输出true,否则就输出false。

解法:把大小王定义为0,先把数组排序,然后统计数组中0的个数,如果相邻数字之间的空缺总数小于等于0的个数,则是连续的

时间复杂度:O(n)

# -*- coding:utf-8 -*-
class Solution:
    def IsContinuous(self, numbers):
        if not numbers:
            return False
        numbers.sort()
        # 统计数组中0的个数
        zero_num = len([i for i in numbers if i == 0])
        # 统计数组中相邻数字之间的空缺总数
        gap_num = 0
        # 第一个非0位下标
        small = zero_num
        # 第二个非0位下标
        big = small + 1
        while big < len(numbers):
            # 如果两数相等,不可能是顺子
            if numbers[small] == numbers[big]:
                return False
            gap_num += numbers[big] - numbers[small] - 1
            small = big
            big += 1
        # 如果相邻数字之间的空缺总数小于等于0的个数,则是连续的
        return gap_num <= zero_num

剑指offer:圆圈中最后剩下的数字

问题描述:从0到n-1围成一个圈,从0开始删除第m个数字,求最后剩下的数字

解法:分析数字规律,

时间复杂度:

剑指offer:

问题描述:

解法:

时间复杂度:

发布了93 篇原创文章 · 获赞 119 · 访问量 5万+

猜你喜欢

转载自blog.csdn.net/qq_18310041/article/details/95196225