剑指offer_面试题14:剪绳子,面试题15:二进制中1的个数(2的整数次方判断、汉明距离),面试题16:数值的整数次方,面试题17:打印从1到最大的n位数(字符串相加、链表相加)

[面试题14:剪绳子](https://leetcode.com/problems/integer-break/description/

在这里插入图片描述

① 动态规划
  • 问题分析:
  1. 绳子的长度至少为2,剪绳子时,要求至少剪成2段。
  2. 如果绳子长度为2,则最大乘积为1 x 1 = 1;如果绳子长度为3,则最大乘积为1 x 2 = 2
  3. 如果使用动态规划,则dp[i]表示长度为i的绳子,剪断后能获得的最大乘积。
  4. 长度为i的绳子,其最大值应该是 m a x ( d p [ 1 ] ∗ d p [ i − 1 ] , d p [ 2 ] ∗ d p [ i − 2 ] , . . . , d p [ i − 1 ] ∗ d p [ 1 ] ) max(dp[1]*dp[i-1], dp[2]*dp[i-2], ... , dp[i-1]*dp[1]) max(dp[1]dp[i1],dp[2]dp[i2],...,dp[i1]dp[1])
  5. 可以发现:到了i/2后,后面的乘积就是重复的,只需要计算到i/2即可。
  • 动态数组的初始化至关重要:
  1. 如果长度小于等于3,直接返回对其裁剪的结果:f(2)=1, f(3)=2。
  2. 否则,初始化动态数组时,表明长度为1, 2, 3的绳子都是子段,值应该为本身,而非上面的值。
  • 动态规划的代码如下:
public int integerBreak(int n) {
    // 不能将其作为子段的话,直接返回
    if (n==2){
        return 1;
    }
    if (n==3){
        return 2;
    }
    // 初始化动态数组,如果长度小于1直接返回n
    int[] dp = new int[n + 1];
    dp[1] = 1;
    // 作为子段,不能再细分了
    dp[2] = 2;
    dp[3] = 3;
    for (int i = 4; i <= n; i++) {
        int max = 0;
        for (int j = 1; j <= i / 2; j++) {
            int temp = j * dp[i - j];
            if (max < temp) {
                max = temp;
                dp[i] = temp;
            }
        }
    }
    return dp[n];
}
② 贪心算法
  • 贪心算法:每次都按照结果最大的情况进行取值。
  1. 如果绳子长度大于4,每次都先按照3进行裁剪。
  2. 直到绳子长度小于等于4,按照2进行裁剪。
  3. 这样,最后的每段绳子的乘积将会是最大的。
  4. 记录整个绳子被剪成长度为3和长度为2的次数,就可以知道整个绳子的最大乘积: 3 i + 2 j 3^i+2^j 3i+2j
  5. 特殊情况: 如果最后按照长度3进行裁剪,最后剩下长度为1,说明租后一次不应该按照3进行裁剪。这时,长度3的裁剪次数应该减1。
  • 代码如下:
public int integerBreak(int n) {
    // 不能将其作为字段的话,直接返回
    if (n == 2) {
        return 1;
    }
    if (n == 3) {
        return 2;
    }
    int time3 = n / 3;
    // 最后剩下长度为1的情况需要特殊处理
    if (n - 3 * time3 == 1) {
        time3--;
    }
    int time2 = (n - 3 * time3) / 2;
    return (int) (Math.pow(3, time3)) * (int)(Math.pow(2, time2));
}
面试题15:二进制中1的个数

在这里插入图片描述

  1. 通过将flag从1开始进行左移,分别计算每一位上是否为1.
  2. 计算汉明重量,公式:n = n & (n-1)
  • 注意: 如果通过flag移位进行计算,则判断的条件是:相与的结果不为0。
public int NumberOf1(int n) {
    int count = 0, flag = 1;
    while (flag != 0) {
        if ((n & flag)!= 0) {
            count++;
        }
        flag=flag<<1;
    }
    return count;
}
  • 汉明重量:
public int NumberOf1(int n) {
    int count = 0;
    while (n!=0){
        count++;
        n=n&(n-1);// 计算汉明重量
    }
    return count;
}
变形1:判断一个整数是否为2的整数次方
  • 如果一个整数为2的整数次方,则该整数的二进制数中有且仅有一位为1,其他位为0。
  • 如果将整数减去1,原来的1变为0,其低位全部变为1。这时两个数相与,结果为0。
  • 例如:4的二进制为100,减去1后的二进制为011,相与的结果100 & 011 = 000
  • 一句话代码:
return (num & (num - 1)) == 0 ? true : false;
变形2 —— leetcode461:汉明距离(两个数的二进制的不同位数)
  • 先异或,再求汉明重量:
  1. 两个数直接异或,得到的结果中1的个数表示不同二进制的位数。
  2. 对异或的结果求二进制1的个数,即求汉明重量。
  • 代码如下:
public int hammingDistance(int x, int y) {
    int temp = x ^ y;
    int count = 0;
    while (temp != 0) {
        count++;
        temp = temp & (temp - 1);
    }
    return count;
}
面试题16:数值的整数次方

在这里插入图片描述

  • 求一个数的整数次方,传统的思考:
  1. 如果指数为负数m,要求底数不能0,先求底数的|m|次方,再求倒数即为最后的结果。
  2. 如果指数为0,要求底数不能为0, n 0 = 1 ( n 不为 0 ) n^0=1(n不为0) n0=1(n不为0)
  3. 如果指数为正整数,直接连乘。
  • 使用二分的思想:
  1. 如求m的16次方,可以先求m的8次方;求m的8次方,可以先求m的4次方;以此类推。
  2. m的2次方直接相乘,求得了m的2次方,可以求m的4次方,然后可以求m的8次方,以此类推。
  3. 即应该使用队规的思想。
    在这里插入图片描述
  • 代码如下,由于题目中限定base和exponent不同时为0,感觉很多特殊情况都不用考虑。
public double Power(double base, int exponent) {
    boolean flag = false;
    if (exponent < 0) {
        flag = true;
        exponent = -exponent;
    }
    double result = helper(base, exponent);
    return flag ? 1 / result : result;
}

public double helper(double base, int exponent) {
    if (exponent == 0) {
        return 1;
    }
    if (exponent == 1) {
        return base;
    }
    // 递归求解n/2次方
    double pow = helper(base, exponent >> 1);
    pow = pow * pow;
    if ((exponent & 1) != 0) {// 指数为奇数
        pow = pow * base;
    }
    return pow;
}
面试题17:打印从1到最大的n位数

在这里插入图片描述

  • 通过递归实现全排列:
  1. 先构造长度为n的字符串,然后每次固定末尾的数字,通过全排列构造整数。
  2. 全排列时,每次都向index + 1位添加数组,然后递归添加下一位,直到index指向字符串的最高位。
  3. 打印时,需要去除字符串中的前导0:sb.toString().replaceFirst("^0*", "")。同时,需要排除全为0的情况。
//打印1到最大的n位数的主方法
public void printDigits(int n) {
    if (n <= 0) {
        System.out.println("n必须大于等于1");
        return;
    }
    StringBuilder sb = new StringBuilder();
    // 构造长度为n的字符串
    for (int i = 0; i < n; i++) {
        sb.append("0");
    }
    for (int i = 0; i < 10; i++) {
        sb.setCharAt(0, (char) (i + '0'));
        // 递归通过全排列构造整数
        constructDigit(sb, 0, n);
    }
}

//打印1到最大的n位数的主方法
public void constructDigit(StringBuilder sb, int index, int n) {
    if (index == (n - 1)) {
        printDigit(sb);
        return;
    }

    for (int i = 0; i < 10; i++) {
        sb.setCharAt(index + 1, (char) (i + '0'));
        constructDigit(sb, index + 1, n);
    }
}

public void printDigit(StringBuilder sb) {
    String result = sb.toString().replaceFirst("^0*", "");
    if (result.length() != 0) {
        System.out.println(result);
    }
}
变形1 —— leetcode415:字符串相加
  • 过程分析:
  1. 将字符串按末位对齐,进行按位加法,将进位标识初始化为0,每次相加时,一定要加上进位。
  2. 根据相加的结果更新进位和sum,注意: 不够10时,进位仍需要更新为0,防止前一次为1
  3. 字符串的长度可能不相同,按位相加后,某一个字符串有剩余,直接将其与进位相加作为新的结果。
  4. 为了避免某一个字符串长度0时的按位相加,可以在一开始就进行特殊情况处理
  • 代码如下:
public String addStrings(String num1, String num2) {
    // 特殊情况,不用计算
    if (num1.length() == 0) {
        return num2;
    }
    if (num2.length() == 0) {
        return num1;
    }
    // 末位对齐,向高位依次进行加法计算
    int m = num1.length() - 1, n = num2.length() - 1;
    String result = "";
    int flag = 0;
    while (m >= 0 && n >= 0) {
        int a = num1.charAt(m--) - '0';
        int b = num2.charAt(n--) - '0';
        int sum = a + b + flag;// 加上进位
        flag = sum / 10;
        sum = sum % 10;
        result = sum + result;
    }
    while (m >= 0) {//num1有剩余
        int a = num1.charAt(m--) - '0';
        int sum = a + flag;
        flag = sum / 10;
        sum = sum % 10;
        result = sum + result;
    }
    while (n >= 0) {//num2有剩余
        int a = num2.charAt(n--) - '0';
        int sum = a + flag;
        flag = sum / 10;
        sum = sum % 10;
        result = sum + result;
    }
    if (flag == 1) {
        result = 1 + result;
    }
    return result;
}
变形2 —— leetcode2:链表相加
  • 注意: 数字是逆序存储的,第一位是最低位,新的链表应该使用尾插法加入新的计算结果。
  • 代码:
public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
    // 某一个链表为null,不用进行加法计算
    if (l1 == null) {
        return l2;
    }
    if (l2 == null) {
        return l1;
    }
    int flag = 0;
    ListNode head = new ListNode(0);// 数字在链表中逆序存储,需要使用尾插法
    ListNode p = head;
    while (l1 != null && l2 != null) {
        int sum = l1.val + l2.val + flag;
        flag = sum / 10; // 这样计算进位更简洁
        sum = sum % 10;
        // 创建新节点,使用尾插法插入到新链表中
        p.next = new ListNode(sum);
        p = p.next;
        // 更新l1和l2
        l1 = l1.next;
        l2 = l2.next;
    }
    // l1有剩余
    while (l1 != null) {
        int sum = l1.val + flag;
        flag = sum / 10;
        sum = sum % 10;
        p.next = new ListNode(sum);
        p = p.next;
        l1 = l1.next;
    }
    // l2有剩余
    while (l2 != null) {
        int sum = l2.val + flag;
        flag = sum / 10;
        sum = sum % 10;
        p.next = new ListNode(sum);
        p = p.next;
        l2 = l2.next;
    }
    if (flag == 1) {
        p.next = new ListNode(1);
        p = p.next;
    }
    p.next = null;
    return head.next;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值