《剑指 Offer》(第 2 版) 题解(Python 语言实现)第 61-68 题


第 61 题:扑克牌顺子(掌握位运算占位的技巧)

传送门:扑克牌的顺子

从扑克牌中随机抽 5 张牌,判断是不是一个顺子,即这 5 张牌是不是连续的。

2~10 为数字本身,A 为 1 ,J 为 11,Q 为 12,K 为 13,大小王可以看做任意数字。

为了方便,大小王均以0来表示,并且假设这副牌中大小王均有两张。

样例1:

输入:[8,9,10,11,12]

输出:true

样例2:

输入:[0,8,9,11,12]

输出:true

思路:基本思路就是把不符合题目要求的情况全部排除掉,剩下的就是正确的了。

python 代码:

class Solution(object):
    def isContinuous(self, numbers):
        """
        :type numbers: List[int]
        :rtype: bool
        """
        size = len(numbers)
        if size != 5:
            return False
        # 最小值和最大值都设置成一个不可能取到的值
        min_val = 14
        max_val = -1

        flag = 0
        for num in numbers:
            if not 0 <= num <= 13:
                return False
            if num == 0:
                continue
            # 右移:看看这一位是不是用过了
            if (flag >> num) & 1 == 1:
                return False

            # 左移:表示这一位我现在要占用
            flag = flag | (1 << num)

            min_val = min(min_val, num)
            max_val = max(max_val, num)
            if max_val - min_val >= 5:
                return False
        return True

Python 代码:

class Solution:
    def IsContinuous(self, numbers):
        # write code here
        if len(numbers) < 5:
            return False
        max_num = -1
        min_num = 14
        flag = 0
        for number in numbers:
            if number < 0 or number > 13:
                return False
            if number == 0:
                continue
            if (flag >> number) & 1 == 1:
                return False
            flag |= 1 << number
            if number < min_num:
                min_num = number
            if number > max_num:
                max_num = number
            if max_num - min_num >= 5:
                return False
        return True

Java 代码:

import java.util.Arrays;

public class Solution {

    public boolean isContinuous(int[] numbers) {
        int len = numbers.length;
        if (len != 5) {
            return false;
        }
        Arrays.sort(numbers);
        // "0" 的个数
        int zeroCount = 0;
        // 当前数与后一个数的距离,距离为 1,表示是顺子
        int diffDistance = 0;
        for (int i = 0; i < 4; i++) {
            if (numbers[i] == 0) {
                zeroCount++;
                continue;
            }
            if (numbers[i] == numbers[i + 1]) {
                return false;
            } else {
                diffDistance += (numbers[i + 1] - numbers[i] - 1);
            }

        }
        return zeroCount >= diffDistance;
    }
}

Java 代码:

private boolean isOrderly(int[] number) {
    //1、非空判断
    if (number == null){
        return false;
    }

    //2、计算 0 的个数
    int zero = 0;
    for (int num : number) {
        if (num == 0) {
            zero++;
        }
    }

    //3、将数组排序
    Arrays.sort(number);

    //4、排序完成之后 从非零数据进行两两判断
    int small = zero;
    int big = small + 1;
    int numberGap = 0;

    //5、排除一种情况  相邻数据不相等情况
    //进行循环的基础条件
    while (big < number.length) {
        if (number[small] == number[big]) {
            return false;//有对子的存在
        }
        //统计相邻之间的空格
        numberGap += number[big] - number[small] - 1;
        //所有的数据进行后移一位
        small = big;
        big++;
    }
    //判断所有的间隔与0的个数 小于或者等于则是有序的 否则则是无序的
    return numberGap <= zero;
}

第 62 题:孩子们的游戏(圆圈中最后剩下的数)

传送门:圆圈中最后剩下的数字

0, 1, …, n-1 这 n 个数字 (n>0) 排成一个圆圈,从数字 0 开始每次从这个圆圈里删除第 m 个数字。

求出这个圆圈里剩下的最后一个数字。

样例:

输入:n=5 , m=3

输出:3

思路1:使用环形链表模拟约瑟夫环。注意特例,即 n = = 0 n==0 n==0 成立,没有数字的时候,返回 $ -1$ 即可。

image-20190107152233987

Python 写法:记住这种写法,两个要点。

class Solution:
    def lastRemaining(self, n, m):
        # 特判
        if n == 0 and m == 0:
            return -1

        l = [i for i in range(n)]
        bt = 0
        while len(l) > 1:
            
            # 在这一行模拟约瑟夫环操作
            # 1、m - 1 :因为当前数字算 1 个,走 m - 1 步
            # 2、len(l):每次删去一个数,所以得动态取
            bt = (bt + m - 1) % len(l)
            
            l.pop(bt)
        return l[0]

思路2:书上说的,使用数学方法。

第 63 题:股票的最大利润

传送门:股票的最大利润

假设把某股票的价格按照时间先后顺序存储在数组中,请问买卖交易该股票可能获得的利润是多少?

例如一只股票在某些时间节点的价格为 [9, 11, 8, 5, 7, 12, 16, 14]

如果我们能在价格为 5 的时候买入并在价格为 16 时卖出,则能收获最大的利润 11。

样例:

输入:[9, 11, 8, 5, 7, 12, 16, 14]

输出:11

思路:在过往的股价中找到最低价,“当前股价 - 最低价”为获利。遍历过程中,找到这个获利的最大值即可。由于只允许做一次股票买卖交易,枚举每一天作为卖出的日子,买入日子一定在卖出日子之前,为了获利最多,希望买入的日子的股票价格尽可能低。用 minnum 记录第 0 0 0 到 第 i i i 天的最低价格,则在第 i i i 天卖出的最大获利为 nums[i] - minnum,枚举 i i i 找到最大获利。

image-20190107152100532

Python 代码:

class Solution(object):
    def maxDiff(self, nums):
        """
        :type nums: List[int]
        :rtype: int
        """
        l = len(nums)
        if l == 0:
            return 0

        min_val = nums[0]
        max_profit = 0
        for i in range(1, l):
            min_val = min(min_val, nums[i])
            max_profit = max(max_profit, nums[i] - min_val)
        return max_profit

同 LeetCode 第 121 题。

传送门:121. 买卖股票的最佳时机

给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。

如果你最多只允许完成一笔交易(即买入和卖出一支股票),设计一个算法来计算你所能获取的最大利润。

注意你不能在买入股票前卖出股票。

示例 1:

输入: [7,1,5,3,6,4]
输出: 5
解释: 在第 2 天(股票价格 = 1)的时候买入,在第 5 天(股票价格 = 6)的时候卖出,最大利润 = 6-1 = 5 。
  注意利润不能是 7-1 = 6, 因为卖出价格需要大于买入价格。

示例 2:

输入: [7,6,4,3,1]
输出: 0
解释: 在这种情况下, 没有交易完成, 所以最大利润为 0。

Java 代码:

public class Solution2 {

    // 留意这个解法的语义

    public int maxProfit(int[] prices) {
        int buy = Integer.MIN_VALUE;
        int sell = 0;
        for (int price : prices) {
            // 在当前以及之前如果执行了买操作,能够得到的利润的最大值
            buy = Math.max(buy, -price);
            // 在当前以及之前如果执行了卖操作,能够得到的利润的最大值
            sell = Math.max(sell, buy + price);
        }
        return sell;
    }

    public static void main(String[] args) {
        int[] prices = {7, 1, 5, 3, 6, 4};
        Solution2 solution2 = new Solution2();
        int maxProfit = solution2.maxProfit(prices);
        System.out.println(maxProfit);
    }
}

区别于 LeetCode 第 122 题。

传送门: 122. 买卖股票的最佳时机 II

给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。

设计一个算法来计算你所能获取的最大利润。你可以尽可能地完成更多的交易(多次买卖一支股票)。

**注意:**你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。

示例 1:

输入: [7,1,5,3,6,4]
输出: 7
解释: 在第 2 天(股票价格 = 1)的时候买入,在第 3 天(股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5-1 = 4 。
  随后,在第 4 天(股票价格 = 3)的时候买入,在第 5 天(股票价格 = 6)的时候卖出, 这笔交易所能获得利润 = 6-3 = 3 。

示例 2:

输入: [1,2,3,4,5]
输出: 4
解释: 在第 1 天(股票价格 = 1)的时候买入,在第 5 天 (股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5-1 = 4 。
  注意你不能在第 1 天和第 2 天接连购买股票,之后再将它们卖出。
  因为这样属于同时参与了多笔交易,你必须在再次购买前出售掉之前的股票。

示例 3:

输入: [7,6,4,3,1]
输出: 0
解释: 在这种情况下, 没有交易完成, 所以最大利润为 0。

参考资料:从零开始学贪心算法 - CSDN博客 https://blog.csdn.net/qq_32400847/article/details/51336300

第 64 题:求 1 + 2 + 3 + … + n

传送门:AcWing:求 1 + 2 + 3 + … + n

1+2+…+n,要求不能使用乘除法、for、while、if、else、switch、case 等关键字及条件判断语句(A?B:C)。

样例:

输入:10

输出:55

Java 代码:

image-20190107004547112

image-20190107102626959

分析:等差数列求和的通项公式如下:
s = n ( n + 1 ) 2 = n 2 + n 2 s=\cfrac{n(n+1)}{2} = \cfrac{n^2+n}{2} s=2n(n+1)=2n2+n
将上面的公式中的运算换成只用加法、乘方运算、位运算。

Python 代码:

class Solution(object):
    def getSum(self, n):
        """
        :type n: int
        :rtype: int
        """

        return (n ** 2 + n) >> 1

第 65 题:不用加减乘除做加法

传送门: 不用加减乘除做加法

写一个函数,求两个整数之和,要求在函数体内不得使用+、-、×、÷ 四则运算符号。

样例:

输入:num1 = 1 , num2 = 2

输出:3

思路:不用加减乘除,那就只能用位运算了。

image-20190107153340742

下面是交换两个数的特殊写法,了解一下。

image-20190107153459638

Python 代码:在 Python 内部对整数的处理分为普通整数和长整数,普通整数长度为机器位长,通常都是 32 32 32 位,超过这个范围的整数就自动当长整数处理,而长整数的范围几乎完全没限制。

Python 代码:

class Solution(object):
    def add(self, num1, num2):
        """
        :type num1: int
        :type num2: int
        :rtype: int
        """
        while num2 != 0:
            # 不进位加法
            temp = num1 ^ num2
            # 加法进位
            num2 = (num1 & num2) << 1
            # 把高于 32 位的 1 全部变成 0
            num1 = temp & 0xFFFFFFFF
        return num1 if num1 >> 31 == 0 else num1 - (1 << 32)

说明:Python 中 int 类型的最大值是 0x7fffffff

Python 代码:与上面的写法等价

class Solution(object):
    def add(self, num1, num2):
        """
        :type num1: int
        :type num2: int
        :rtype: int
        """

        while True:
            # 不进位加法
            s = num1 ^ num2
            # 计算进位
            carry = num1 & num2

            # 手动把高于 32 位的部分变成 0
            num1 = s & 0xFFFFFFFF
            num2 = carry << 1

            if carry == 0:
                break
        # 如果是正数和 0 ,就直接返回这个正数好了
        if num1 >> 31 == 0:
            return num1
        # 如果是负数
        return num1 - (1 << 32)

Java 代码:

public class Solution {

    public int Add(int num1, int num2) {
        int sum = 0;
        while (true) {
            // 计算个位
            sum = num1 ^ num2;
            
            int carry = num1 & num2;
            if (carry == 0) {
                break;
            }
            num1 = sum;
            // 计算进位
            num2 = carry << 1;

        }
        return sum;
    }

    public static void main(String[] args) {
        Solution solution = new Solution();
        int add = solution.Add(14, 15);
        System.out.println(add);
    }
}

还可以用“全加器”实现。

image-20190124124711571

Python 代码:

class Solution(object):
    def add(self, num1, num2):
        """
        :type num1: int
        :type num2: int
        :rtype: int
        """
        # 特判
        if num1 == 0 or num2 == 0:
            return max(num1, num2)

        res = 0
        # 进位
        carry = 0
        for i in range(32):
            a = num1 & (1 << i)
            b = num2 & (1 << i)
            # 不进位的和
            s_ = (a ^ b) ^ carry
            # 下面计算进位,三者之中,任意两者同为 1 的时候,就可以进位
            carry = (a & b) | (a & carry) | (b & carry)
            carry <<= 1
            res += s_
        if res >> 31 == 0:
            return res
        return res - (1 << 32)

第 66 题:构建乘积数组

传送门:构建乘积数组

给定一个数组A[0, 1, …, n-1],请构建一个数组B[0, 1, …, n-1],其中 B 中的元素B[i] = A[0] × A[1] × … × A[i - 1] × A[i + 1] × … × A[n - 1]

不能使用除法。

样例:

输入:[1, 2, 3, 4, 5]

输出:[120, 60, 40, 30, 24]

思考题

  • 能不能只使用常数空间?(除了输出的数组之外)

image-20190107153549498

思路:使用矩阵法求解,将矩阵分为上三角矩阵和下三角矩阵,分别求乘积。

image-20190107153617970

Python 代码:

class Solution(object):
    def multiply(self, A):
        """
        :type A: List[int]
        :rtype: List[int]
        """

        l = len(A)
        if l == 0:
            return []
        b = [None for _ in range(l)]

        b[0] = 1
        # 计算下三角连乘的结果
        for i in range(1, l):
            b[i] = b[i - 1] * A[i - 1]

        temp = 1
        for i in range(l - 2, -1, -1):
            temp *= A[i + 1]
            b[i] *= temp
        return b

Python 代码:

# 66、构建乘积数组

class Solution(object):
    def multiply(self, A):
        """
        :type A: List[int]
        :rtype: List[int]
        """

        l = len(A)
        if l == 0:
            return []
        b = [1 for _ in range(l)]
        temp = 1
        for index in range(l):
            b[index] *= temp
            temp *= A[index]
        temp = 1
        for index in range(l - 1, -1, -1):
            b[index] *= temp
            temp *= A[index]
        return b

Java 代码:

image-20190107153732474

C++ 代码:

class Solution {
public:
    vector<int> multiply(const vector<int>& A) {
        vector<int>left(A.size(),1);
        vector<int>right(A.size(),1);
        for(int i = 1;i<A.size();i++){
            left[i] = A[i-1]*left[i-1];
        }
        for(int i = A.size()-2;i>=0;i--){
            right[i] = A[i+1]*right[i+1];
        }
        vector<int>B(A.size(),0);
        for(int i = 0;i<A.size();i++){
            B[i] = left[i]*right[i];
        }
        return B;
    }
};

作者:cornerCao
链接:https://www.acwing.com/solution/AcWing/content/759/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

第 67 题:把字符串转换成整数

传送门:把字符串转换成整数

请你写一个函数StrToInt,实现把字符串转换成整数这个功能。

当然,不能使用atoi或者其他类似的库函数。

样例:

输入:"123"

输出:123

注意:

你的函数应满足下列条件:

  1. 忽略所有行首空格,找到第一个非空格字符,可以是 ‘+/−+/−’ 表示是正数或者负数,紧随其后找到最长的一串连续数字,将其解析成一个整数;
  2. 整数后可能有任意非数字字符,请将其忽略;
  3. 从前往后遍历时,如果第一段连续非空格字符串不是一个有效的整数表示,则返回0;
  4. 如果整数大于INT_MAX(2^31 − 1),请返回INT_MAX;如果整数小于INT_MIN(−2^31) ,请返回INT_MIN;

Java 代码:

image-20190107103003656

同 LeetCode 第 8 题。

传送门:8. 字符串转换整数 (atoi)

请你来实现一个 atoi 函数,使其能将字符串转换成整数。

首先,该函数会根据需要丢弃无用的开头空格字符,直到寻找到第一个非空格的字符为止。

当我们寻找到的第一个非空字符为正或者负号时,则将该符号与之后面尽可能多的连续数字组合起来,作为该整数的正负号;假如第一个非空字符是数字,则直接将其与之后连续的数字字符组合起来,形成整数。

该字符串除了有效的整数部分之后也可能会存在多余的字符,这些字符可以被忽略,它们对于函数不应该造成影响。

注意:假如该字符串中的第一个非空格字符不是一个有效整数字符、字符串为空或字符串仅包含空白字符时,则你的函数不需要进行转换。

在任何情况下,若函数不能进行有效的转换时,请返回 0。

说明:

假设我们的环境只能存储 32 位大小的有符号整数,那么其数值范围为 [−231, 231 − 1]。如果数值超过这个范围,qing返回 INT_MAX (231 − 1) 或 INT_MIN (−231) 。

示例 1:

输入: "42"
输出: 42

示例 2:

输入: "   -42"
输出: -42
解释: 第一个非空白字符为 '-', 它是一个负号。
  我们尽可能将负号与后面所有连续出现的数字组合起来,最后得到 -42 。

示例 3:

输入: "4193 with words"
输出: 4193
解释: 转换截止于数字 '3' ,因为它的下一个字符不为数字。

示例 4:

输入: "words and 987"
输出: 0
解释: 第一个非空字符是 'w', 但它不是数字或正、负号。
  因此无法执行有效的转换。

示例 5:

输入: "-91283472332"
输出: -2147483648
解释: 数字 "-91283472332" 超过 32 位有符号整数范围。 
  因此返回 INT_MIN (−231) 。

Python 代码:Python 代码比较特别,要注意,符号位不在第 32 32 32 位上

class Solution(object):
    def strToInt(self, str):
        """
        :type str: str
        :rtype: int
        """

        # 去掉左右空格
        s = str.strip()

        l = len(s)
        if l == 0:
            return 0

        # 遍历指针
        index = 0
        # 符号
        sign = 1
        # 第 1 位符号位
        s_sign = s[0]

        # 最终结果
        res = 0

        INT_MIN = -1 << 31
        INT_MAX = (1 << 31) - 1

        # 符号位是正或负号的时候 index 都加 1
        if s_sign == '+':
            index += 1
        elif s_sign == '-':
            index += 1
            sign = -1

        for i in range(index, l):
            c = s[i]
            if c.isdigit():
                cint = ord(c) - ord('0')
                res = res * 10 + cint
                if res * sign > INT_MAX:
                    break
            else:
                break

        res *= sign

        if res > INT_MAX:
            return INT_MAX
        elif res < INT_MIN:
            return INT_MIN
        return res


if __name__ == '__main__':
    solution = Solution()
    str = '2147483647'
    result = solution.strToInt(str)
    print(result)

第 68 题:树中两个节点的最近公共祖先

传送门:树中两个结点的最低公共祖先

给出一个二叉树,输入两个树节点,求它们的最低公共祖先。

一个树节点的祖先节点包括它本身。

注意:

  • 输入的二叉树不为空;
  • 输入的两个节点一定不为空,且是二叉树中的节点;

样例:

二叉树 [8, 12, 2, null, null, 6, 4, null, null, null, null] 如下图所示:

   8
 / \
12  2
     / \
   6   4
  1. 如果输入的树节点为 2 和 12,则输出的最低公共祖先为树节点 8 。

  2. 如果输入的树节点为 2 和 6 ,则输出的最低公共祖先为树节点 2 。

同 LeetCode 第 236 题:二叉树的最近公共祖先。

传送门:236. 二叉树的最近公共祖先

给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。

百度百科中最近公共祖先的定义为:“对于有根树 T 的两个结点 p、q,最近公共祖先表示为一个结点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。”

例如,给定如下二叉树: root = [3,5,1,6,2,0,8,null,null,7,4]

示例 1:

输入: root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 1
输出: 3
解释: 节点 5 和节点 1 的最近公共祖先是节点 3。

示例 2:

输入: root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 4
输出: 5
解释: 节点 5 和节点 4 的最近公共祖先是节点 5。因为根据定义最近公共祖先节点可以为节点本身。

说明:

  • 所有节点的值都是唯一的。
  • p、q 为不同节点且均存在于给定的二叉树中。

分析:1、如果有指向父结点的指针;2、没有指向父结点的指针。

image-20190110150856835

image-20190107152146560

Python 代码:

class Solution(object):
    def lowestCommonAncestor(self, root, p, q):
        """
        :type root: TreeNode
        :type p: TreeNode
        :type q: TreeNode
        :rtype: TreeNode
        """

        if root is None:
            return None
        if root == p or root == q:
            return root
        left = self.lowestCommonAncestor(root.left, p, q)
        right = self.lowestCommonAncestor(root.right, p, q)
        if left and right:
            return root
        if left is None:
            return right
        if right is None:
            return left
        return None

Java 代码:

image-20190107103531946


参考资料:

B 站 UP 主“大雪菜”直播:https://www.acwing.com/activity/content/introduction/5/。

《剑指offer》面试题的Python实现

剑指Offer系列刷题笔记汇总
https://blog.csdn.net/c406495762/article/details/79247243#链表-8道

https://github.com/gatieme/CodingInterviews

剑指Offer系列刷题笔记汇总

第 2 版 Java 实现代码仓库:https://github.com/cris1313/SwordForOffer

白夜行515

https://blog.csdn.net/baiye_xing/article/details/78428561

剑指Offer——编程题的Java实现(更新完毕……)

https://blog.csdn.net/u011464124/article/details/76706011

剑指offer题目java实现

https://www.cnblogs.com/ysw-go/p/6272551.html

极客学院

http://wiki.jikexueyuan.com/project/for-offer/question-twenty-seven.html

https://blog.csdn.net/column/details/codingintervieww.html>

image-20190107005346026


题目:String 空字符串替换:从后向前替换,边复制,边覆盖,不要思维定势

单链表问题应该引起重视:链表问题如果不想“穿针引线”那么就让递归去完成!

从尾到头打印链表:(1)用栈(2)递归和栈密不可分(递归深度很深的时候,不要这么做)

第 8 题:二叉树的下一个结点

Java 代码:

image-20190107112051561

57_1、队列的最大值

传送门:

(本节完)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值