Day6-剑指offer

一、不用加减乘除做加法

1.1题目

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

示例:

输入: a = 1, b = 1
输出: 2

本题考察对位运算的灵活使用,即使用位运算实现加法。
设两数字的二进制形式 a, b ,其求和 s = a + b,a(i)代表 a 的二进制第 i位,则分为以下四种情况:
在这里插入图片描述

观察发现,无进位和异或运算 规律相同,进位与运算规律相同(并需左移一位)。因此,无进位和 n与进位 c的计算公式如下:
在这里插入图片描述
(和 s)=(非进位和 n)+(进位 c )。即可将 s = a + b转化为:

s=a+b → s=n+c

循环求 n和 c,直至进位 c = 0;此时 s = n,返回 n 即可。

在这里插入图片描述

Q : 若数字 a和 b中有负数,则变成了减法,如何处理?
A : 在计算机系统中,数值一律用 补码 来表示和存储。补码的优势: 加法、减法可以统一处理(CPU只有加法器)。因此,以上方法 同时适用于正数和负数的加法 。

1.2代码

class Solution {
    public int add(int a, int b) {
        while(b!=0){   // 当进位为 0 时跳出
            int c=(a&b)<<1;  //c表示进位并左移1位
            a=a^b;    //a表示非进位和
            b=c;
        }
        return a;
    }
}

二、矩阵中的路径

2.1题目

给定一个 m x n 二维字符网格 board 和一个字符串单词 word 。如果 word 存在于网格中,返回 true ;否则,返回 false 。
单词必须按照字母顺序,通过相邻的单元格内的字母构成,其中“相邻”单元格是那些水平相邻或垂直相邻的单元格。同一个单元格内的字母不允许被重复使用。

例如,在下面的 3×4 的矩阵中包含单词 “ABCCED”(单词中的字母已标出)
在这里插入图片描述

示例 1:

输入:board = [[“A”,“B”,“C”,“E”],[“S”,“F”,“C”,“S”],[“A”,“D”,“E”,“E”]], word = “ABCCED”
输出:true示例 2:

示例2:

输入:board = [[“a”,“b”],[“c”,“d”]], word = “abcd”
输出:false

思路:

  • dfs + 回溯;
  • 使用二维布尔数组记录之前的位置是否已经被访问过,如果已经被访问过的话,则在 dfs 的过程中,直接 return false 即可。也就是说,此路已经不通了;
  • 如果当前遍历到的字符不等于 board[i][j] 位置上的字符,那么说明此路也是不通的,因此返回 false;
  • 至于递归结束的条件:如果指针 start 能够来到 word 的最后一个字符,那么说明能够在矩阵 board 中找到一条路径,此时返回 true;
  • 在遍历到当前 board[i][j] 的时候,首先应将该位置的 visited[i][j] 设置为 true,表明访问过;
    然后,递归地去 board[i][j] 的上下左右四个方向去找,剩下的路径;
  • 在 dfs 的过程当中,如果某条路已经不通了,那么那么需要回溯,也就是将 visited[i][j] 位置的布尔值重新赋值为 fasle;
  • 最后,返回 ans 即可。

2.2代码

class Solution {
    public boolean exist(char[][] board, String word) {
        if(word==null||board.length==0||board[0].length==0){
            return false;
        }

        char a[]=word.toCharArray();
        boolean visited[][]=new boolean[board.length][board[0].length];
        for(int i=0;i<board.length;i++){
            for(int j=0;j<board[i].length;j++){
                if(dfs(board,a,visited,i,j,0)){
                    return true;
                }
            }
        }
        return false;
    }
    public boolean dfs(char board[][],char a[],boolean visited[][],int i, int j, int start){
        if(i < 0 || i >= board.length || j < 0 || j >= board[0].length 
                || a[start] != board[i][j] || visited[i][j]){
            return false;
        }
        if(start==a.length-1){
            return true;
        }

        visited[i][j]=true;
        boolean ans=false;
        ans= dfs(board, a, visited, i + 1, j, start + 1) 
           || dfs(board, a, visited, i - 1, j, start + 1)
           || dfs(board, a, visited, i, j + 1, start + 1)
           || dfs(board, a, visited, i, j - 1, start + 1);
        visited[i][j] = false;
        return ans;
    }
}

三、剪绳子 II

3.1题目

给你一根长度为 n 的绳子,请把绳子剪成整数长度的 m 段(m、n都是整数,n>1并且m>1),每段绳子的长度记为 k[0],k[1]…k[m-1] 。请问 k[0]k[1]…*k[m-1] 可能的最大乘积是多少?例如,当绳子的长度是8时,我们把它剪成长度分别为2、3、3的三段,此时得到的最大乘积是18。

示例 1:

输入: 2
输出: 1
解释: 2 = 1 + 1, 1 × 1 = 1

示例 2:

输入: 10
输出: 36
解释: 10 = 3 + 3 + 4, 3 × 3 × 4 = 36

思路:

最优: 3 。把绳子尽可能切为多个长度为 3 的片段,留下的最后一段绳子的长度可能为 0,1,2三种情况。
次优: 2 。若最后一段绳子长度为 2 ;则保留,不再拆为 1+1 。
最差:1 。若最后一段绳子长度为 1 ;则应把一份 3 + 1 替换为 2 + 2,因为2×2>3×1。

3.2代码

class Solution {
    public int cuttingRope(int n) {
        if(n<=3) return n-1;
        int a=n/3,b=n%3;
        if(b==0) return (int)Math.pow(3,a);
        if(b==1) return (int)Math.pow(3,a-1)*4;
        return (int)Math.pow(3,a)*2;
        
    }
}

四、数值的整数次方

4.1题目

实现 pow(x, n) ,即计算 x 的 n 次幂函数(即,xn)。不得使用库函数,同时不需要考虑大数问题。

示例 1:

输入:x = 2.00000, n = 10
输出:1024.00000

示例 2:

输入:x = 2.10000, n = 3
输出:9.26100

示例 3:

输入:x = 2.00000, n = -2
输出:0.25000
解释:2-2 = 1/22 = 1/4 = 0.25

思路:
1.由于考虑负数,先把负数转变为正数。

//将正数n和负数n都给转换为正数n
//注意:Java 代码中 int32 变量n∈[−2147483648,2147483647]
//因此当 n = -2147483648 时执行 n = -n 会因越界而赋值出错
//我们此处一开始就把 n 用 long 存储

2.对于正数可以让底数成倍增加 指数成倍减少做到原数不变
当指数为奇数时 先把一个指数分离出来,底数先乘以一个自身,这样指数可变为偶数。

4.2代码

class Solution {
    public double myPow(double x, int n) {
        long b=n;
        if(n<0){
            b=-b;
            x=1/x;
        }
        return curb(x,b);
    }
    public double curb(double x,long b){
        double s=1.0;
        while(b>0){
            if((b&1)==1){
                s=s*x;
            }
            b=b/2;
            x=x*x;
        }
        return s;
    }
}

五、数组中数字出现的次数

5.1题目

一个整型数组 nums 里除两个数字之外,其他数字都出现了两次。请写程序找出这两个只出现一次的数字。要求时间复杂度是O(n),空间复杂度是O(1)。

示例 1:

输入:nums = [4,1,4,6]
输出:[1,6] 或 [6,1]

示例 2:

输入:nums = [1,2,10,4,1,4,3,3]
输出:[2,10] 或 [10,2]

5.2代码

class Solution {
    public int[] singleNumbers(int[] nums) {
        //因为相同的数字异或为0,任何数字与0异或结果是其本身。
        //所以遍历异或整个数组最后得到的结果就是两个只出现一次的数字异或的结果:即 z = x ^ y
        int z = 0;  
        for(int i : nums) z ^= i;
        //我们根据异或的性质可以知道:z中至少有一位是1,否则x与y就是相等的。
        //我们通过一个辅助变量m来保存z中哪一位为1.(可能有多个位都为1,我们找到最低位的1即可)。
        //举个例子:z = 10 ^ 2 = 1010 ^ 0010 = 1000,第四位为1.
        //我们将m初始化为1,如果(z & m)的结果等于0说明z的最低为是0
        //我们每次将m左移一位然后跟z做与操作,直到结果不为0.
        //此时m应该等于1000,同z一样,第四位为1.
        int m = 1;
        while((z & m) == 0) m <<= 1;
        //我们遍历数组,将每个数跟m进行与操作,结果为0的作为一组,结果不为0的作为一组
        //例如对于数组:[1,2,10,4,1,4,3,3],我们把每个数字跟1000做与操作,可以分为下面两组:
        //nums1存放结果为0的: [1, 2, 4, 1, 4, 3, 3]
        //nums2存放结果不为0的: [10] (碰巧nums2中只有一个10,如果原数组中的数字再大一些就不会这样了)
        //此时我们发现问题已经退化为数组中有一个数字只出现了一次
        //分别对nums1和nums2遍历异或就能得到我们预期的x和y
        int x = 0, y = 0;
        for(int i : nums) {
            //这里我们是通过if...else将nums分为了两组,一边遍历一遍异或。
            //跟我们创建俩数组nums1和nums2原理是一样的。
            if((i & m) == 0) x ^= i;
            else y ^= i;
        }
        return new int[]{x, y};
    }
}

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值