剑指offer 1-11

AcWing 13. 找出数组中重复的数字

给定一个长度为 n 的整数数组 nums,数组中所有的数字都在 0∼n−1 的范围内。

数组中某些数字是重复的,但不知道有几个数字重复了,也不知道每个数字重复了几次。

请找出数组中任意一个重复的数字。

注意:如果某些数字不在 0∼n−1 的范围内,或数组中不包含重复数字,则返回 -1;

样例
给定 nums = [2, 3, 5, 4, 3, 2, 6, 7]。

返回 2 或 3。

from collections import defaultdict
class Solution(object):
    def duplicateInArray(self, nums):
        """
        :type nums: List[int]
        :rtype int
        """
        mp = defaultdict(int)
        res = -1
        for it in nums:
            if it >= len(nums) or it <0: #遗漏
                return -1
            if mp[it] != 0:
                res = it
            mp[it] += 1
        return res # 遗漏

(数组遍历) O(n)
首先遍历一遍数组,如果存在某个数不在0到n-1的范围内,则返回-1。

下面的算法的主要思想是把每个数放到对应的位置上,即让 nums[i] = i。

从前往后遍历数组中的所有数,假设当前遍历到的数是 nums[i]=x,那么:

如果x != i && nums[x] == x,则说明 x出现了多次,直接返回 x即可;
如果nums[x] != x,那我们就把 x 交换到正确的位置上,即 swap(nums[x], nums[i]),交换完之后如果nums[i] != i,则重复进行该操作。由于每次交换都会将一个数放在正确的位置上,所以swap操作最多会进行 n 次,不会发生死循环。

循环结束后,如果没有找到任何重复的数,则返回-1。

时间复杂度分析
每次swap操作都会将一个数放在正确的位置上,最后一次swap会将两个数同时放到正确位置上,一共只有 n 个数和 n 个位置,所以swap最多会进行 n−1次。所以总时间复杂度是 O(n)。

class Solution {
public:
    int duplicateInArray(vector<int>& nums) {
        int n = nums.size();
        for (auto x : nums)
            if (x < 0 || x >= n)
                return -1;
        for (int i = 0; i < n; i ++ ) {
            while (nums[nums[i]] != nums[i]) swap(nums[i], nums[nums[i]]);
            if (nums[i] != i) return nums[i];
        }
        return -1;
    }
};

AcWing 14. 不修改数组找出重复的数字

给定一个长度为 n+1 的数组nums,数组中所有的数均在 1∼n 的范围内,其中 n≥1。

请找出数组中任意一个重复的数,但不能修改输入的数组。

样例
给定 nums = [2, 3, 5, 4, 3, 2, 6, 7]。

返回 2 或 3。
思考题:如果只能使用 O(1) 的额外空间,该怎么做呢?
yxc大佬
(分治,抽屉原理) O(nlogn)
这道题目主要应用了抽屉原理和分治的思想。

抽屉原理:n+1 个苹果放在 n 个抽屉里,那么至少有一个抽屉中会放两个苹果。

用在这个题目中就是,一共有 n+1 个数,每个数的取值范围是1到n,所以至少会有一个数出现两次。

然后我们采用分治的思想,将每个数的取值的区间[1, n]划分成[1, n/2]和[n/2+1, n]两个子区间,然后分别统计两个区间中数的个数。
注意这里的区间是指 数的取值范围,而不是 数组下标。即这题跟题目给的数的顺序无关

划分之后,左右两个区间里一定至少存在一个区间,区间中数的个数大于区间长度。
这个可以用反证法来说明:如果两个区间中数的个数都小于等于区间长度,那么整个区间中数的个数就小于等于n,和有n+1个数矛盾。

因此我们可以把问题划归到左右两个子区间中的一个,而且由于区间中数的个数大于区间长度,根据抽屉原理,在这个子区间中一定存在某个数出现了两次。

依次类推,每次我们可以把区间长度缩小一半,直到区间长度为1时,我们就找到了答案。

复杂度分析
时间复杂度:每次会将区间长度缩小一半,一共会缩小 \(O(logn)\)次。每次统计两个子区间中的数时需要遍历整个数组,时间复杂度是 O(n)。所以总时间复杂度是 \(O(nlogn)\)
空间复杂度:代码中没有用到额外的数组,所以额外的空间复杂度是 O(1)。

二分经典问题

class Solution(object):
    def duplicateInArray(self, nums):
        """
        :type nums: List[int]
        :rtype int
        """
        n = len(nums)
        l = 1
        r = n 
        
        while l < r:
            mid = (l + r) >> 1
            s = 0
            for it in nums:
                if it >= l and it <= mid:
                    s += 1
            # print(mid, s)
            if s > mid - l + 1:
                r = mid
            else:
                l = mid + 1 
        return l

AcWing 15. 二维数组中的查找

在一个二维数组中,每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。

请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。

样例

输入数组:

[ [1,2,8,9], [2,4,9,12], [4,7,10,13], [6,8,11,15] ]

如果输入查找数值为7,则返回true,

如果输入查找数值为5,则返回false。

yxc 大佬做法
算法
(单调性扫描) \(O(n+m)\)
核心在于发现每个子矩阵右上角的数的性质:

如下图所示,x左边的数都小于等于x,x下边的数都大于等于x。
在这里插入图片描述

因此我们可以从整个矩阵的右上角开始枚举,假设当前枚举的数是 xx:

  • 如果 x等于target,则说明我们找到了目标值,返回true;
  • 如果 x 小于target,则 x左边的数一定都小于target,我们可以直接排除当前一整行的数;
  • 如果 x 大于target,则 x下边的数一定都大于target,我们可以直接排序当前一整列的数;

排除一整行就是让枚举的点的横坐标加一,排除一整列就是让纵坐标减一。
当我们排除完整个矩阵后仍没有找到目标值时,就说明目标值不存在,返回false。

时间复杂度分析
每一步会排除一行或者一列,矩阵一共有 n 行,mm 列,所以最多会进行 n+m 步。所以时间复杂度是 O(n+m)。

class Solution(object):
    def searchArray(self, array, target):
        """
        :type array: List[List[int]]
        :type target: int
        :rtype: bool
        """
        if len(array) == 0 or len(array[0]) == 0:
            return False
            
        i = 0
        j = len(array[0]) - 1
        
        while i < len(array) and j >=0:
            if target == array[i][j]:
                return True
            elif target > array[i][j]:
                i += 1
            elif target < array[i][j]:
                j -= 1
        
        return False

AcWing 16. 替换空格

请实现一个函数,把字符串中的每个空格替换成"%20"。

你可以假定输入字符串的长度最大是1000。
注意输出字符串的长度可能大于1000。

样例
输入:"We are happy."

输出:"We%20are%20happy."

class Solution(object):
    def replaceSpaces(self, s):
        """
        :type s: str
        :rtype: str
        """
        return s.replace(" ","%20")
        

AcWing 17. 从尾到头打印链表

输入一个链表的头结点,按照 从尾到头 的顺序返回节点的值。

返回的结果用数组存储。

样例
输入:[2, 3, 5]
返回:[5, 3, 2]

# Definition for singly-linked list.
# class ListNode(object):
#     def __init__(self, x):
#         self.val = x
#         self.next = None
class Solution(object):
    def printListReversingly(self, head):
        """
        :type head: ListNode
        :rtype: List[int]
        """
        res = []
        while head != None:
            res.append(head.val)
            head = head.next
        res.reverse()
        return res

AcWing 18. 重建二叉树

输入一棵二叉树前序遍历和中序遍历的结果,请重建该二叉树。

注意:

二叉树中每个节点的值都互不相同;
输入的前序遍历和中序遍历一定合法;
样例

给定: 前序遍历是:[3, 9, 20, 15, 7] 中序遍历是:[9, 3, 15, 20, 7]

返回:[3, 9, 20, null, null, 15, 7, null, null, null, null]

返回的二叉树如下所示:
3
/ \
9 20
/ \
15 7

算法
(递归) O(n)
递归建立整棵二叉树:先递归创建左右子树,然后创建根节点,并让指针指向两棵子树。

具体步骤如下:

  1. 先利用前序遍历找根节点:前序遍历的第一个数,就是根节点的值;
  2. 在中序遍历中找到根节点的位置 k,则 k左边是左子树的中序遍历,右边是右子树的中序遍历;
  3. 假设左子树的中序遍历的长度是 l,则在前序遍历中,根节点后面的 l个数,是左子树的前序遍历,剩下的数是右子树的前序遍历;
  4. 有了左右子树的前序遍历和中序遍历,我们可以先递归创建出左右子树,然后再创建根节点;

时间复杂度分析
我们在初始化时,用哈希(unordered_map<int,int>)记录每个值在中序遍历中的位置,这样我们在递归到每个节点时,在中序遍历中查找根节点位置的操作,只需要 O(1)的时间。此时,创建每个节点需要的时间是 O(1),所以总时间复杂度是 O(n)。

# Definition for a binary tree node.
# class TreeNode(object):
#     def __init__(self, x):
#         self.val = x
#         self.left = None
#         self.right = None
class Solution(object):
    
    def buildTree(self, preorder, inorder):
        """
        :type preorder: List[int]
        :type inorder: List[int]
        :rtype: TreeNode
        """
        if len(preorder) == 0:
            return None
            
        pos = [None for i in range(max(inorder) + 1)]
        for i in range(len(inorder)):
            pos[inorder[i]] = i

        def build(il, ir, pl, pr):
            if pl > pr:
                return None
            root = TreeNode(preorder[pl])
            k = pos[root.val]
            
            if il < k: root.left = build(il, k - 1, pl + 1, pl + (k - 1 - il) + 1)
            if ir > k: root.right = build(k+1 , ir, pl + k - il + 1, pr)
            
            return root
        return build(0, len(preorder) - 1, 0, len(preorder) - 1)
                    

AcWing 19. 二叉树的下一个节点

给定一棵二叉树的其中一个节点,请找出中序遍历序列的下一个节点。

注意:

如果给定的节点是中序遍历序列的最后一个,则返回空节点;
二叉树一定不为空,且给定的节点一定不是空节点;
样例

假定二叉树是:[2, 1, 3, null, null, null, null], 给出的是值等于2的节点。

则应返回值等于3的节点。

解释:该二叉树的结构如下,2的后继节点是3。
2
/ \
1 3

算法
(模拟) O(h)
这道题目就是让我们求二叉树中给定节点的后继。

分情况讨论即可,如下图所示:

  1. 如果当前节点有右儿子,则右子树中最左侧的节点就是当前节点的后继。比如F的后继是H;
  2. 如果当前节点没有右儿子,则需要沿着father域一直向上找,找到第一个是其father左儿子的节点,该节点的father就是当前节点的后继。比如当前节点是D,则第一个满足是其father左儿子的节点是F,则C的father就是D的后继,即F是D的后继。
    在这里插入图片描述

时间复杂度分析
不论往上找还是往下找,总共遍历的节点数都不大于树的高度。所以时间复杂度是 O(h),其中 h 是树的高度。

# Definition for a binary tree node.
# class TreeNode(object):
#     def __init__(self, x):
#         self.val = x
#         self.left = None
#         self.right = None
#         self.father = None
class Solution(object):
    def inorderSuccessor(self, q):
        """
        :type q: TreeNode
        :rtype: TreeNode
        """
        
        if q.right != None:
            
            p = q.right
            while p.left != None:
                p = p.left
            return p
        else:
            p = q
            while p.father != None and p.father.right == p:
                p = p.father
            return p.father

AcWing 20. 用两个栈实现队列

请用栈实现一个队列,支持如下四种操作:

push(x) – 将元素x插到队尾;
pop() – 将队首的元素弹出,并返回该元素;
peek() – 返回队首元素;
empty() – 返回队列是否为空;
注意:

你只能使用栈的标准操作:push to top,peek/pop from top, size 和 is empty;
如果你选择的编程语言没有栈的标准库,你可以使用list或者deque等模拟栈的操作;
输入数据保证合法,例如,在队列为空时,不会进行pop或者peek等操作;
样例
MyQueue queue = new MyQueue();

queue.push(1);
queue.push(2);
queue.peek(); // returns 1
queue.pop(); // returns 1
queue.empty(); // returns false

class MyQueue(object):

    def __init__(self):
        """
        Initialize your data structure here.
        """
        self.sta = []

    def push(self, x):
        """
        Push element x to the back of queue.
        :type x: int
        :rtype: void
        """
        self.sta.append(x)

    def pop(self):
        """
        Removes the element from in front of queue and returns that element.
        :rtype: int
        """
        
        return self.sta.pop(0)

    def peek(self):
        """
        Get the front element.
        :rtype: int
        """
        return self.sta[0]

    def empty(self):
        """
        Returns whether the queue is empty.
        :rtype: bool
        """
        if len(self.sta) == 0:
            return True
        return False
        


# Your MyQueue object will be instantiated and called as such:
# obj = MyQueue()
# obj.push(x)
# param_2 = obj.pop()
# param_3 = obj.peek()
# param_4 = obj.empty()

AcWing 21. 斐波那契数列

输入一个整数 n ,求斐波那契数列的第 n 项。

假定从0开始,第0项为0。(n<=39)

样例 输入整数 n=5

返回 5

class Solution(object):
    def Fibonacci(self, n):
        """
        :type n: int
        :rtype: int
        """
        if n == 0: return 0
        a = 0
        b = 1
        for i in range(2, n+1):
            a, b = b, a + b
        return b

生成器
生成器讲解

class Solution(object):
    def Fibonacci(self, n):
        """
        :type n: int
        :rtype: int
        """
        if n == 0: return 0
        def f(n):
            a = 0
            b = 1
            for i in range(n):
                yield b # 生成器
                a, b = b, a + b
        res = 0
        for it in f(n):
            res = it
        return res

AcWing 22. 旋转数组的最小数字

把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。

输入一个升序的数组的一个旋转,输出旋转数组的最小元素。

例如数组{3,4,5,1,2}为{1,2,3,4,5}的一个旋转,该数组的最小值为1。

数组可能包含重复项。

注意:数组内所含元素非负,若数组大小为0,请返回-1。

样例 输入:nums=[2,2,2,0,1]

输出:0

class Solution:
    def findMin(self, nums):
        """
        :type nums: List[int]
        :rtype: int
        """
        if len(nums) == 0:
            return -1
        else:
            return min(nums)

yxc 大佬
算法
(二分) O(n)
为了便于分析,我们先将数组中的数画在二维坐标系中,横坐标表示数组下标,纵坐标表示数值,如下所示:
在这里插入图片描述

图中水平的实线段表示相同元素。

我们发现除了最后水平的一段(黑色水平那段)之外,其余部分满足二分性质:竖直虚线左边的数满足 nums[i]≥nums[0];而竖直虚线右边的数不满足这个条件。
分界点就是整个数组的最小值。

所以我们先将最后水平的一段删除即可。

另外,不要忘记处理数组完全单调的特殊情况:

当我们删除最后水平的一段之后,如果剩下的最后一个数大于等于第一个数,则说明数组完全单调。

时间复杂度分析
二分的时间复杂度是 O(logn),删除最后水平一段的时间复杂度最坏是 O(n),所以总时间复杂度是 O(n)。

class Solution:
    def findMin(self, nums):
        """
        :type nums: List[int]
        :rtype: int
        """
        if len(nums) == 0:
            return -1
        n = len(nums) - 1
        
        while nums[n] == nums[0]:
            n -= 1
            
        l = 0
        r = n
        
        while l < r:
            mid = (l + r ) >> 1
            if nums[mid] >= nums[0]:
                l = mid + 1
            else :
                r = mid
        return min(nums[0], nums[l])
            

AcWing 23. 矩阵中的路径

请设计一个函数,用来判断在一个矩阵中是否存在一条包含某字符串所有字符的路径。

路径可以从矩阵中的任意一个格子开始,每一步可以在矩阵中向左,向右,向上,向下移动一个格子。

如果一条路径经过了矩阵中的某一个格子,则之后不能再次进入这个格子。

注意:

输入的路径不为空;
所有出现的字符均为大写英文字母;

样例 matrix= [
["A","B","C","E"],
["S","F","C","S"],
["A","D","E","E"]
]

str="BCCE" , return "true"

str="ASAE" , return "false"

算法
DFS
\(O(n^{2} 3^k)\)
在深度优先搜索中,最重要的就是考虑好搜索顺序。

我们先枚举单词的起点,然后依次枚举单词的每个字母。
过程中需要将已经使用过的字母改成一个特殊字母,以避免重复使用字符。

时间复杂度分析:单词起点一共有 \(n^2\) 个,单词的每个字母一共有上下左右四个方向可以选择,但由于不能走回头路,所以除了单词首字母外,仅有三种选择。所以总时间复杂度是 \(O(n^{2} 3^k)\)

class Solution(object):
    def hasPath(self, matrix, string):
        """
        :type matrix: List[List[str]]
        :type string: str
        :rtype: bool
        """
  
        
        for i in range(len(matrix)):
            for j in range(len(matrix[i])):
                st = [[0 for j in range(len(matrix[i]))] for i in range(len(matrix))]
                if self.dfs(i, j, 0, st, matrix, string) == True:
                    return True
        return False

     
    def dfs(self, i, j, u, st, matrix, string):
        dx = [0, 0, 1, -1]
        dy = [1, -1, 0, 0]
        
        if matrix[i][j] != string[u] : return False
        st[i][j] = 1
        #print(i, j, matrix[i][j], u)
        #print(st)
        if u == len(string) - 1: return True
        
            
        for t in range(4):
            x = i + dx[t]
            y = j + dy[t]
            if x < len(matrix) and x >= 0 and  y < len(matrix[x]) and y >= 0 and st[x][y] == 0 :
                st[x][y] = 1
                if self.dfs(x, y, u + 1, st, matrix, string) == True:
                    return True
                st[x][y] = 0
        return False
         
    
                    
                
posted @ 2021-11-21 21:25  pxlsdz  阅读(281)  评论(0编辑  收藏  举报