剑指offer 第12-22题

AcWing 24. 机器人的运动范围

地上有一个 m 行和 n 列的方格,横纵坐标范围分别是 0∼m−1 和 0∼n−1。

一个机器人从坐标0,0的格子开始移动,每一次只能向左,右,上,下四个方向移动一格。

但是不能进入行坐标和列坐标的数位之和大于 k 的格子。

请问该机器人能够达到多少个格子?

样例1
输入:k=7, m=4, n=5

输出:20
样例2
输入:k=18, m=40, n=40

输出:1484

解释:当k为18时,机器人能够进入方格(35,37),因为3+5+3+7 = 18。
但是,它不能进入方格(35,38),因为3+5+3+8 = 19。
注意:

0<=m<=50
0<=n<=50
0<=k<=100

时间复杂度:\(o(nm)\)
宽搜与深搜(容易爆栈)

class Solution(object):
    def bfs(self, x, y, k):
        def f(x, y):
            res = 0
            for i in str(x):
                res += int(i)
            for j in str(y):
                res += int(j)
            return res
        dx = [0, 0, 1, -1]
        dy = [1, -1, 0, 0]
        hh = 0
        tt = -1
        q = [[] for i in range(self.cols * self.rows + 100)]
        tt += 1
        q[tt] = [0, 0]
        res = 0
        
        while hh <= tt:
            
            t = q[hh]
      
            hh += 1
            if self.st[t[0]][t[1]] == 1:
                continue
            self.st[t[0]][t[1]] = 1
            res += 1

            for i in range(4):
                x1 = t[0] + dx[i]
                y1 = t[1] + dy[i]
                if x1 < self.rows and x1 >= 0 and y1 < self.cols and y1 >=0 and f(x1, y1) <= k and self.st[x1][y1] == 0:
                    tt += 1
                    q[tt] = [x1, y1]
                    
        return res 
    def movingCount(self, threshold, rows, cols):
        """
        :type threshold: int
        :type rows: int
        :type cols: int
        :rtype: int
        """ 
        self.w = 0
        self.st = [[0 for j in range(cols)] for i in range(rows)]
        if rows == 0 or cols ==0 :
            return 0
        self.rows = rows
        self.cols = cols
        return self.bfs(0, 0, threshold) 
  

深搜

class Solution(object):
    def dfs(self, x, y, k):
        self.w += 1
        dx = [0, 0, 1, -1]
        dy = [1, -1, 0, 0]
        
        res = 1
        for t in range(4):
            x1 = dx[t] + x
            y1 = dy[t] + y
            #
            #if self.w == 100:
            #    return 0;
            def f(x, y):
                res = 0
                for i in str(x):
                    res += int(i)
                for j in str(y):
                    res += int(j)
                return res
            if x1 < self.rows and x1 >= 0 and y1 < self.cols and y1 >=0 and f(x1, y1) <= k and self.st[x1][y1] == 0:
                # print(x1, y1)
                self.st[x1][y1] = 1
                res += self.dfs(x1, y1, k)
                # self.st[x1][y1] = 0
                
        return res 
    def movingCount(self, threshold, rows, cols):
        """
        :type threshold: int
        :type rows: int
        :type cols: int
        :rtype: int
        """ 
        self.w = 0
        self.st = [[0 for j in range(cols)] for i in range(rows)]
        if rows == 0 or cols ==0 :
            return 0
        self.rows = rows
        self.cols = cols
        self.st[0][0] = 1
        return self.dfs(0, 0, threshold) 
  

AcWing 25. 剪绳子

给你一根长度为 n 绳子,请把绳子剪成 m 段(m、n 都是整数,2≤n≤58 并且 m≥2)。

每段的绳子的长度记为k[0]、k[1]、……、k[m]。k[0]k[1] … k[m] 可能的最大乘积是多少?

例如当绳子的长度是8时,我们把它剪成长度分别为2、3、3的三段,此时得到最大的乘积18。

样例
输入:8

输出:18

算法
(数学) O(n)

这道题目是数学中一个很经典的问题。
下面我们给出证明:

首先把一个正整数 N拆分成若干正整数只有有限种拆法,所以存在最大乘积。
假设 N=n1+n2+…+nk,并且 n1×n2×…×nk是最大乘积。

  • 显然1不会出现在其中;
  • 如果对于某个 ini≥5,那么把 ni 拆分成 3 + (ni−3),我们有 3(ni−3) = 3ni−9 > ni;推出不能有大于5的数。
  • 如果 ni=4,拆成 2+2乘积不变,所以不妨假设没有4
  • 如果有三个以上的2,那么 3×3>2×2×2,所以替换成3乘积更大;
    综上,选用尽量多的3,直到剩下2或者4时,用2

时间复杂度分析:当 n比较大时,n 会被拆分成 ⌈n/3⌉ 个数,我们需要计算这么多次减法和乘法,所以时间复杂度是 O(n)

class Solution(object):
    def maxProductAfterCutting(self,length):
        """
        :type length: int
        :rtype: int
        """
        res = 1
        if length % 3 == 0:
            while length > 0:
                length -= 3
                res *= 3
        elif length % 3 == 1:
            length -= 4
            while length > 0:
                length -= 3
                res *= 3
            res *= 4
        elif length % 3 == 2:
            length -= 2
            if length == 0:
                return 1
            while length > 0:
                length -= 3
                res *= 3
            res *= 2
        return res

AcWing 26. 二进制中1的个数

正数补码是本身,
负数补码 :符号位不变,逐位求反(从头开始), 加1。

思路1:
lowbit : n&(n-1)的结果:n最右边的1变成0,比如n为6
110&101-》100
循环直到n为0为止
上述思想C++可以直接过。
为什么python中负数需要和 0xFFFFFFFF 做与操作?
在计算机中,所有的数字都是使用补码存储起来的。由于Python没有位数这个概念,所以得到二进制表示需要多一点操作,即将位数限制在32位,通过和一个32位的全1数字按位与运算即可。但对于负数来说,直接bin(-1)是不能得到其补码的,而是得到了1的原码前面加上了负号,即-0b1。则通过和一个32位的全1数字按位与运算可得到其补码二进制表示对应的十进制数(按位与运算把符号位的1视为了数字)。Python要使用 n & 0xffffffff 得到一个数的补码

class Solution(object):
    def NumberOf1(self,n):
        """
        :type n: int
        :rtype: int
        """
        res = 0
        if n < 0:
            n = n & 0xFFFFFFFF
        while n != 0 :
            n -= n & (-n)
            res += 1
            
            
        return res

思路2
(位运算) O(logn)
迭代进行如下两步,直到 n 变成0为止:

如果 n在二进制表示下末尾是1,则在答案中加1;
将 n右移一位,也就是将 nn 在二进制表示下的最后一位删掉;
这里有个难点是如何处理负数。
在C++中如果我们右移一个负整数,系统会自动在最高位补1,这样会导致 n 永远不为0,就死循环了。
解决办法是把 n 强制转化成无符号整型,这样 n 的二进制表示不会发生改变,但在右移时系统会自动在最高位补0。

时间复杂度
每次会将 n除以2,最多会除 logn 次,所以时间复杂度是 O(logn)。

class Solution {
public:
    int NumberOf1(int n) {
        int res = 0;
        unsigned int un = n; 
        while (un) res += un & 1, un >>= 1;
        return res;
    }
};

AcWing 27. 数值的整数次方

实现函数double Power(double base, int exponent),求base的 exponent次方。

不得使用库函数,同时不需要考虑大数问题。

注意:

不会出现底数和指数同为0的情况
当底数为0时,指数一定为正

样例1 输入:10 ,2
输出:100

样例2
输入:10 ,-2 >
输出:0.01

注意一下负数即可
https://pxlsdz.blog.csdn.net/article/details/109124180

class Solution(object):
    def Power(self, base, exponent):
        """
        :type base: float
        :type exponent: int
        :rtype: float
        """
        b = exponent
        exponent = abs(exponent)
        res = 1
        while exponent > 0:
            if exponent & 1 == 1:
                res *= base
                
            base = base * base
            exponent >>= 1
       
        if b < 0:
            res = 1 / res
        return res

AcWing 28. 在O(1)时间删除链表结点

算法
(链表) O(1)

给定单向链表的一个节点指针,定义一个函数在O(1)时间删除该结点。

假设链表一定存在,并且该节点一定不是尾节点。

样例 输入:链表 1->4->6->8
删掉节点:第2个节点即6(头节点为第0个节点)

输出:新链表 1->4->8
由于是单链表,我们不能找到前驱节点,所以我们不能按常规方法将该节点删除。
我们可以换一种思路,将下一个节点的值复制到当前节点,然后将下一个节点删除即可。

时间复杂度
只有常数次操作,所以时间复杂度是 O(1)。

# Definition for singly-linked list.
# class ListNode(object):
#     def __init__(self, x):
#         self.val = x
#         self.next = None

class Solution(object):
    def deleteNode(self, node):
        """
        :type node: ListNode
        :rtype: void 
        """
        node.val = node.next.val
        node.next = node.next.next

AcWing 29. 删除链表中重复的节点

算法
(线性扫描) O(n)

为了方便处理边界情况,我们定义一个虚拟元素 dummy 指向链表头节点。
然后从前往后扫描整个链表,每次扫描元素相同的一段,如果这段中的元素个数多于1个,则将整段元素直接删除。

时间复杂度
整个链表只扫描一遍,所以时间复杂度是 O(n)。

# Definition for singly-linked list.
# class ListNode(object):
#     def __init__(self, x):
#         self.val = x
#         self.next = None

class Solution(object):
    def deleteDuplication(self, head):
        """
        :type head: ListNode
        :rtype: ListNode
        """
        dumpy = ListNode(None)
        dumpy.next = head
        p = dumpy
        while p != None:
            q = p.next
            while p.next != None and q.next != None and p.next.val == q.next.val:
                q = q.next
            
            if p.next != q:
                p.next = q.next
            else: p = p.next
        
        return dumpy.next
/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode* deleteDuplication(ListNode* head) {
        auto dummy = new ListNode(-1);
        dummy->next = head;

        auto p = dummy;
        while (p->next) {
            auto q = p->next;
            while (q && p->next->val == q->val) q = q->next;

            if (p->next->next == q) p = p->next;
            else p->next = q;
        }

        return dummy->next;
    }
};

AcWing 30. 正则表达式匹配

题目描述
请实现一个函数用来匹配包括’.’和’*’的正则表达式。

模式中的字符’.’表示任意一个字符,而’*’表示它前面的字符可以出现任意次(含0次)。

在本题中,匹配是指字符串的所有字符匹配整个模式。

例如,字符串”aaa”与模式”a.a”和”abaca”匹配,但是与”aa.a”和”ab*a”均不匹配。

样例 输入:

s="aa" p="a*"

输出:true

算法
(动态规划) O(nm)

状态表示:f[i][j]为所有s[1...i]与p[1....j]匹配的方案的集合。
属性:集合是否为空
状态转移:

  1. 如果p[j]不是通配符'*'

    \[f[i][j] = (s[i] == p[j]\ or\ p[j] == '.')\ \&\&\ f[i-1][j-1]== 1 \]

  2. 如果p[j+1]是通配符'*'*表示字母个数来划分集合
  • *表示的字母个数为0个时:

\[f[i][j] = f[i][j-2] \]

  • *表示的字母个数为1个时:

    \[f[i][j] = f[i-1][j-2] \&\& s[i] ==p[j-1] \]

  • *表示的字母个数为2个时:
$$f[i][j] = f[i-2][j-2] \&\& s[i] ==p[j-1\&\&s[i-1]==p[j-1]$$

......

类似完全背包的优化方式

\[f[i][j] = (f[i][j-2])|| (f[i-1][j-2] \&\& s[i] ==p[j-1])|| (f[i-2][j-2] \&\& s[i] ==p[j-1]\&\&s[i-1]==p[j-1]) \]

\[f[i-1][j] = (f[i-1][j-2])|| (f[i-2][j-2] \&\& s[i-1] ==p[j-1])|| (f[i-3][j-2] \&\& s[i-1] ==p[j-1\&\&s[i-2]==p[j-1]) \]

后半部都多了\(s[i-1] == p[j-1]\),结合律(A&b|A&c=A&(b|c))即可推出$$f[i][j] =( f[i][j-2])\ ||\ (f[i-1][j] & s[i]== p[j-1])$$
时间复杂度分析:
n表示s的长度,m 表示p的长度,总共 nm个状态,状态转移复杂度 O(1),所以总时间复杂度是 O(nm).

在这里插入图片描述

class Solution(object):
    def isMatch(self, s, p):
        """
        :type s: str
        :type p: str
        :rtype: bool
        """
        n = len(s)
        m = len(p)
        s = " " + s
        p = " " + p
        dp = [[False for j in range(m + 1)] for i in range(n + 1)]
        dp[0][0] = True

        for i in range(0, n + 1):
            for j in range(1, m + 1):
                if j + 1 < m and p[j+1] == "*":
                    continue
            
                if i and p[j] != "*":
                    dp[i][j] = dp[i - 1][j - 1] and (s[i] == p[j] or p[j] == '.')
                elif p[j] == '*':
                    dp[i][j] = dp[i][j - 2] or (i and dp[i - 1][j] and (s[i] == p[j - 1] or p[j - 1] == '.'))
        
        return dp[n][m] 
class Solution(object):
    def isMatch(self, s, p):
        """
        :type s: str
        :type p: str
        :rtype: bool
        """
        n = len(s)
        m = len(p)
        f = [[False] * (m+1) for i in range(n+1)]
        s = ' ' + s
        p = ' ' + p
        f[0][0] = True

        for i in range(n+1):
            for j in range(1, m+1):
                if j + 1 < m and p[j+1] == '*':
                    continue
                if i and p[j] != '*':
                    if s[i] == p[j] or p[j] == '.':
                        f[i][j] = f[i-1][j-1]
                elif p[j] == '*':
                    f[i][j] = f[i][j-2] or (f[i-1][j] and (s[i] == p[j-1] or p[j-1] == '.'))
        return f[n][m]

AcWing 31. 表示数值的字符串

请实现一个函数用来判断字符串是否表示数值(包括整数和小数)。

例如,字符串"+100","5e2","-123","3.1416"和"-1E-16"都表示数值。

但是"12e","1a3.14","1.2.3","+-5"和"12e+4.3"都不是。

注意:

  1. 小数可以没有整数部分,例如.123等于0.123;
  2. 小数点后面可以没有数字,例如233.等于233.0;
  3. 小数点前面和后面可以有数字,例如233.666;
  4. 当e或E前面没有数字时,整个字符串不能表示数字,例如.e1、e1;
  5. 当e或E后面没有整数时,整个字符串不能表示数字,例如12e、12e+5.4;

样例: 输入: "0"

输出: true

class Solution(object):
    def isNumber(self, s):
        """
        :type s: str
        :rtype: bool
        """
        try :
            float(s)
            return True
        except:
            return False

AcWing 32. 调整数组顺序使奇数位于偶数前面

算法
(双指针扫描) O(n)

用两个指针分别从首尾开始,往中间扫描。扫描时保证第一个指针前面的数都是奇数,第二个指针后面的数都是偶数。

每次迭代时需要进行的操作:

  • 第一个指针一直往后走,直到遇到第一个偶数为止;
  • 第二个指针一直往前走,直到遇到第一个奇数为止;
  • 交换两个指针指向的位置上的数,再进入下一层迭代,直到两个指针相遇为止;

时间复杂度
当两个指针相遇时,走过的总路程长度是 n,所以时间复杂度是 O(n)。

class Solution(object):
    def reOrderArray(self, array):
        """
        :type array: List[int]
        :rtype: void
        """
        i = 0
        j = len(array) - 1 
        while i < j:
            while i< j and array[i] & 1 == 1:
                i += 1
                
            while j > i and array[j] & 1 != 1:
                j -= 1
            if i <j : array[i], array[j] = array[j], array[i]
        

AcWing 33. 链表中倒数第k个节点

输入一个链表,输出该链表中倒数第k个结点。

注意:

  • k >= 0; 如果k大于链表长度,则返回 NULL;

样例

输入:链表:1->2->3->4->5 ,k=2

输出:4

# Definition for singly-linked list.
# class ListNode(object):
#     def __init__(self, x):
#         self.val = x
#         self.next = None

class Solution(object):
    def findKthToTail(self, pListHead, k):
        """
        :type pListHead: ListNode
        :type k: int
        :rtype: ListNode
        """
        p = pListHead
        n = 1
        while p != None:
            n += 1
            p = p.next
            
        if k  > n:
            return None
        p = pListHead
        i = 1
     
        while p != None:
            if i == n-k:
                return p
            p = p.next
            i += 1
            

AcWing 34. 链表中环的入口结点

给定一个链表,若其中包含环,则输出环的入口节点。

若其中不包含环,则输出null。

样例
在这里插入图片描述

给定如上所示的链表:

[1, 2, 3, 4, 5, 6]
2
注意,这里的2表示编号是2的节点,节点编号从0开始。所以编号是2的节点就是val等于3的节点。

则输出环的入口节点3.

算法
(链表,快慢指针扫描) O(n)

用两个指针 first,second 分别从起点开始走,first 每次走一步,second 每次走两步。
如果过程中 second走到null,则说明不存在环。否则当 first 和 second相遇后,让 first返回起点,second 待在原地不动,然后两个指针每次分别走一步,当相遇时,相遇点就是环的入口。

在这里插入图片描述
证明:如上图所示,a 是起点,b 是环的入口,c 是两个指针的第一次相遇点,ab 之间的距离是 x,bc 之间的距离是 y, z表示从 cc点顺时针走到 b 的距离。
则第一次相遇时 second所走的距离是 \(x+(y+z)∗n+y\), n表示圈数,同时 second 走过的距离是 first 的两倍,也就是 \(2(x+y)\),所以我们有\(x+(y+z)∗n+y=2(x+y)\),所以 $$x=(n−1)*(y+z)+z$$
y+z是一圈的距离,z = x %(y+z),那么我们让 second从 c 点开始走,走 x步,会恰好走到 b 点;让 first 从 a 点开始走,走 x 步,也会走到 b 点。

# Definition for singly-linked list.
# class ListNode(object):
#     def __init__(self, x):
#         self.val = x
#         self.next = None

class Solution(object):
    def entryNodeOfLoop(self, head):
        """
        :type head: ListNode
        :rtype: ListNode
        """
        first = head
        second = head
        
        while first and second:
            if first == None or second == None:
                return None
            first = first.next
            second = second.next
            if second :
                second = second.next
            if first == second:
                first = head
                while first and second:
                    if first == second:
                        return first
                    first = first.next
                    second = second.next
        return None
                    
                        
                

map o(nlogn) 空间o(n)

class Solution {
public:
    ListNode *entryNodeOfLoop(ListNode *head) {
        map<ListNode*,int> mp;
        while(head)
        {
            if(mp[head])
                return head;
            mp[head]=1;
            head=head->next;
        }
        return NULL;
    }
};
posted @ 2021-11-25 19:34  pxlsdz  阅读(279)  评论(0编辑  收藏  举报