LeetCode面试热题九

166. 分数到小数

给定两个整数,分别表示分数的分子 numerator 和分母 denominator,以 字符串形式返回小数 。
如果小数部分为循环小数,则将循环的部分括在括号内。
如果存在多个答案,只需返回 任意一个 。
对于所有给定的输入,保证 答案字符串的长度小于 104
示例:

输入:numerator = 4, denominator = 333
输出:"0.(012)"

解题思路:先对整数部分进行处理,对于小数部分,做除法的同时,记录余数,若当前余数已经出现过,则说明是循环小数,将上一次余数出现的位置,到当前小数部分,用括号括起来。

public String fractionToDecimal(int numerator, int denominator) {
    long num = numerator;
    long den = denominator;
    StringBuffer sb = new StringBuffer();
    // 符号处理
    if(num < 0 && den > 0 || num > 0 && den < 0){
        sb.append("-");
    }
    // 整数部分的处理
    num = Math.abs(num);
    den = Math.abs(den);
    if(num % den == 0){
        sb.append(num/den);
        return sb.toString();
    }
    if(num > den){
        sb.append(num / den);
        num = num % den;
    }else{
        sb.append("0");
    }
    sb.append(".");
    // 小数部分的处理
    HashMap<Long,Integer> map = new HashMap<>();
    StringBuffer fractionPart = new StringBuffer();
    int index = 0;
    while(num != 0 && !map.containsKey(num)){ 
        map.put(num,index++);
        num = num * 10;
        fractionPart.append(num/den);
        num = num % den;
    }
    if(num != 0){
        index = map.get(num);
        fractionPart.insert(index, '(');
        fractionPart.append(')');
    }
    // 将小数部分添加到整数部分后,返回结果
    sb.append(fractionPart);
    return sb.toString();
}

169. 多数元素

给定一个大小为 n 的数组,找到其中的多数元素。多数元素是指在数组中出现次数 大于 ⌊ n/2 ⌋ 的元素。你可以假设数组是非空的,并且给定的数组总是存在多数元素。
尝试设计时间复杂度为 O(n)、空间复杂度为 O(1) 的算法解决此问题。

示例:

输入:[2,2,1,1,1,2,2]
输出:2

解题思路:投票算法,如果一个数 n 是多数元素,则 n 的个数一定大于其它所有元素的个数。如果数字n遇到不同的数字,则n的票数减1,如果遇到相同的数字,n的票数加1。最后得票最多的,就是多数元素。

public int majorityElement(int[] nums) {
    int res = nums[0];
    int cut = 1;
    for(int i = 1;i<nums.length;i++){
    	// 票数为0,从当前元素重新开始。
        if(cut == 0){
            res = nums[i];
            cut = 1;
            continue;
        }
        if(res == nums[i]){
            cut++;
        }else {
            cut--;
        }
    }
    return res;
}

171. Excel 表列序号

给你一个字符串 columnTitle ,表示 Excel 表格中的列名称。返回该列名称对应的列序号。

A -> 1
B -> 2
C -> 3
...
Z -> 26
AA -> 27
AB -> 28 
...

示例:

输入: columnTitle = "ZY"
输出: 701

输入: columnTitle = "FXSHRXW"
输出: 2147483647
  • 1 <= columnTitle.length <= 7
  • columnTitle 仅由大写英文组成
  • columnTitle 在范围 [“A”, “FXSHRXW”] 内

解题思路:题的本质就是26进制转换为10进制。

public int titleToNumber(String columnTitle) {
    int number = 0;
    for(int i=0;i<columnTitle.length();i++) {
        number = number * 26 + (columnTitle.charAt(i) - 'A' + 1);
    }
    return number;
}

172. 阶乘后的零

给定一个整数 n ,返回 n! 结果中尾随零的数量。
提示 n! = n * (n - 1) * (n - 2) * … * 3 * 2 * 1
0 <= n <= 104
示例:

输入:n = 5
输出:1
解释:5! = 120 ,有一个尾随 0

输入:n = 0
输出:0
解释:0! = 0 ,不含尾随 0

解题思路:先计算出阶乘,然后进行%10判断。
超时

public int trailingZeroes(int n) {
    if(n == 0){
        return 0;
    }
    BigInteger factorial = BigInteger.ONE;
    for (int i = 2; i <= n; i++) {
        factorial = factorial.multiply(BigInteger.valueOf(i));
    }
    int cut = 0;
    while (factorial.mod(BigInteger.TEN).equals(BigInteger.ZERO)) {
        factorial = factorial.divide(BigInteger.TEN);
        cut++;
    }
    return cut;
}

分析:对于阶乘末尾的0的个数。如果当前数 * 10,则末尾会出现一个0。根据这个思路,不必计算出阶乘,只需要判断,在 1 - n中,乘了几个10。
而对于 10 的话,其实也只有 2*5 可以构成,所以我们只需要找有多少对 2 和 5。
含有 2 的因子每两个出现一次,含有 5 的因子每 5 个出现一次,所有 2 出现的个数远远多于 5,换言之找到一个 5,一定能找到一个 2 与之配对。所以我们 只需要找有多少个 5
而对于 5的幂来说,在 52时,有2个5出现,还要再加上一个5,53时,有三个5出现,还要在 52的基础上再加上一个5,以此类推…

public int trailingZeroes(int n) {
    int count = 0;
    while (n > 0) {
        count += n / 5;
        n = n / 5;		// n/5,是寻找 5^2的个数,5^3的个数......
    }
    return count;
}

179. 最大数

给定一组非负整数 nums,重新排列每个数的顺序(每个数不可拆分)使之组成一个最大的整数。
注意:输出结果可能非常大,所以你需要返回一个字符串而不是整数。
示例:

输入:nums = [3,30,34,5,9]
输出:"9534330"

解题思路:本质就是排序问题,先将整数转换为字符串,有字符串 A,字符串B,A+B组成的值和 B+A组成的值比较那个大。

public String largestNumber(int[] nums) {
	// 将数字转换为字符串。
    List<String> str = new ArrayList<>();
    for(int num:nums){
        str.add(String.valueOf(num));
    }
    // 使用自定义比较器,排序
    Collections.sort(str, new Comparator<String>() {
        @Override
        public int compare(String o1, String o2) {
            String o1o2 = new String(o1+o2);
            String o2o1 = new String(o2+o1);
            return o2o1.compareTo(o1o2);
        }
    });
    // 对全为0进行特定判断
    if(str.get(0).equals("0")){
        return "0";
    }
    // 转换为一个字符串
    StringBuffer ans = new StringBuffer();
    for(int i=0;i<str.size();i++){
        ans.append(str.get(i));
    }
    return ans.toString();
}

189. 轮转数组

给你一个数组,将数组中的元素向右轮转 k 个位置,其中 k 是非负数。
示例:

输入: nums = [1,2,3,4,5,6,7], k = 3
输出: [5,6,7,1,2,3,4]
解释:
向右轮转 1: [7,1,2,3,4,5,6]
向右轮转 2: [6,7,1,2,3,4,5]
向右轮转 3: [5,6,7,1,2,3,4]
  • 1 <= nums.length <= 105
  • -231 <= nums[i] <= 231 - 1
  • 0 <= k <= 105

你可以使用空间复杂度为 O(1) 的 原地 算法解决这个问题吗?
方法一:使用额外的数组,在额外的数组中,将原来数组按要求轮转,最后写会原数组。

public void rotate(int[] nums, int k) {
    int len = nums.length;
    if(len == 1){
        return;
    }
    k = k % len;
    int[] tmp = new int[len];
    // 将后面的复制到前面
    for(int i = 0;i < k; i++){
        tmp[i] = nums[len - k + i];
    }
    // 将前面的复制到后面
    for(int i = 0;i < len - k;i++){
        tmp[i + k] = nums[i];
    }
    // 复制回原数组。
    for(int i = 0;i<len;i++){
        nums[i] = tmp[i];
    }
}

方法二:数组三次反转。
例:
nums = [1,2,3,4,5,6,7], k = 3
nums = [7,6,5,4,3,2,1]
nums = [5,6,7,4,3,2,1]
nums = [5,6,7,1,2,3,4]

public void rotate(int[] nums, int k) {
    int len = nums.length;
    if(len == 1){
        return;
    }
    k = k % len;
    reversal(nums,0,len-1);
    reversal(nums,0,k-1);
    reversal(nums,k,len-1);
}
// 方法功能,将[l,r]区间的元素进行反转
public void reversal(int[] nums,int l,int r){ 
    int t;
    while(l < r){
        t = nums[l];
        nums[l] = nums[r];
        nums[r] = t;
        l++;
        r--;
    }
}

方法三:环状替换,数组元素直接替换到最终的位置,并保存被覆盖的值,直到所有元素都替换到正确的位置。
从位置 0 开始,最初令 temp=nums[0]。根据规则,位置 0 的元素会放至 (0+k) mod n 的位置,令 x=(0+k) mod n,此时交换 temp 和 nums[x],完成位置 x 的更新。不断重复此过程,直到回到位置0。然后从位置1继续。

该过程恰好走了整数数量的圈,不妨设为 a 圈;再设该过程总共遍历了 b 个元素。
因此,我们有 an = bk,即 an 一定为 n,k 的公倍数。
an 就是 n,k 的最小公倍数 lcm(n,k)。
因此 b 就为 lcm(n,k)/k。
这说明单次遍历会访问到 lcm(n,k)/k 个元素。
则a为 n/b,即下图
在这里插入图片描述
其中 gcd 指的是最大公约数。

public void rotate(int[] nums, int k) {
    int n = nums.length;
    k = k % n;
    // 遍历的圈数为 coun,k和n的最大公约数
    int count = gcd(k, n);
    for (int start = 0; start < count; ++start) {
    	// current记录起点
        int current = start;
        int prev = nums[start];
        do {
            int next = (current + k) % n;
            int temp = nums[next];
            nums[next] = prev;
            prev = temp;
            current = next;
        } while (start != current);
    }
}
public int gcd(int x, int y) {
    return y > 0 ? gcd(y, x % y) : x;
}

190. 颠倒二进制位

颠倒给定的 32 位无符号整数的二进制位。
示例:

输入:n = 00000010100101000001111010011100
输出:964176192 (00111001011110000010100101000000)
解释:输入的二进制串 00000010100101000001111010011100 表示无符号整数 43261596,
     因此返回 964176192,其二进制表示形式为 00111001011110000010100101000000

请添加图片描述
方法一:逐位颠倒。
从低到高,将每一位逐位颠倒。

public int reverseBits(int n) {
    int rev = 0;
    for (int i = 0; i < 32 && n != 0; ++i) {
    	// n&1 取出首位,左移(31-i)位,rev添加这一位到指定位置。
        rev |= (n & 1) << (31 - i);
        // 无符号左移,将第二位移至首位。
        n >>>= 1;
    }
    return rev;
}

方法二:位运算分治
对于递归的最底层,我们需要交换所有奇偶位:取出所有奇数位和偶数位;将奇数位移到偶数位上,偶数位移到奇数位上。
对于倒数第二层,每两位分一组,按组号取出所有奇数组和偶数组,然后将奇数组移到偶数组上,偶数组移到奇数组上。以此类推。

public class Solution {
    private static final int M1 = 0x55555555; // 01010101010101010101010101010101
    private static final int M2 = 0x33333333; // 00110011001100110011001100110011
    private static final int M4 = 0x0f0f0f0f; // 00001111000011110000111100001111
    private static final int M8 = 0x00ff00ff; // 00000000111111110000000011111111
    public int reverseBits(int n) {
    	/*
    	n = n >>> 1 & M1 | (n & M1) << 1;
    	n >>> 1,n 逻辑右移一位,与 M1按位与。相等于只保留偶数位的值到奇数位上。
    	n & M1,只保留偶数的值,然后左移一位到奇数位上。
    	然后做按位或运算,完成奇数位和偶数位的颠倒 
    	*/
        n = n >>> 1 & M1 | (n & M1) << 1;
        n = n >>> 2 & M2 | (n & M2) << 2;
        n = n >>> 4 & M4 | (n & M4) << 4;
        n = n >>> 8 & M8 | (n & M8) << 8;
        return n >>> 16 | n << 16;
    }
}

191. 位1的个数

编写一个函数,输入是一个无符号整数(以二进制串的形式),返回其二进制表达式中数字位数为 ‘1’ 的个数(也被称为汉明重量)。

示例:

输入:00000000000000000000000000001011
输出:3
解释:输入的二进制串 00000000000000000000000000001011 中,共有三位为 '1'

方法一:逐位检查

public int hammingWeight(int n) {
    int cut = 0;
    for(int i=0;i<32;i++){
        if((n&1) == 1){
            cut++;
        }
        n = n>>>1;
    }
    return cut;
}

方法二:位运算优化
n&n-1,能消去二进制的一位 1。如 6 & 6-1,110&101,结果为100,110中有两位 1,100中有一位1。

public int hammingWeight(int n) {
    int cut = 0;
    while(n!=0){
        n = n&n-1;
        cut++;
    }
    return cut;
}

198. 打家劫舍

你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。
给定一个代表每个房屋存放金额的非负整数数组,计算你 不触动警报装置的情况下 ,一夜之内能够偷窃到的最高金额。
示例:

输入:[2,7,9,3,1]
输出:12
解释:偷窃 1 号房屋 (金额 = 2), 偷窃 3 号房屋 (金额 = 9),接着偷窃 5 号房屋 (金额 = 1)。
     偷窃到的最高金额 = 2 + 9 + 1 = 12

解题思路:动态规划,dp[i]是从0-i个房屋能偷得的最大金额。考虑dp[i]的取值,可以选择偷和不偷第i个房间。如果偷第i个房间,则当前dp[i]的取值为 dp[i-2]+nums[i],如果不偷第i个房间,则dp[i]的取值为dp[i-1]。要使得dp[i]的取值最大,所以 dp[i] = max(dp[i-1],dp[i-2] + nums[i])。
动态规划的边界条件 dp[0] = nums[0],dp[1] = max(nums[0],nums[1])。

public int rob(int[] nums) {
    int len = nums.length;
    if(len == 1){
        return nums[0];
    }
    int[] dp = new int[len];
    dp[0] = nums[0];
    dp[1] = Math.max(nums[0],nums[1]);
    for(int i = 2; i < len;i++){
        dp[i] = Math.max(dp[i-1],dp[i-2] + nums[i]);
    }
    return dp[len-1];
}

200. 岛屿数量

给你一个由 ‘1’(陆地)和 ‘0’(水)组成的的二维网格,请你计算网格中岛屿的数量。
岛屿总是被水包围,并且每座岛屿只能由水平方向和/或竖直方向上相邻的陆地连接形成。
此外,你可以假设该网格的四条边均被水包围。

输入:grid = [
  ["1","1","0","0","0"],
  ["1","1","0","0","0"],
  ["0","0","1","0","0"],
  ["0","0","0","1","1"]
]
输出:3

方法一:DFS或BFS,将相连的1都访问到。最终执行了几次DFS或BFS,岛屿的数量就是几个。

public int numIslands(char[][] grid) {
    int cut = 0;
    int m,n;
    m = grid.length;
    n = grid[0].length;
    for(int i=0;i<m;i++){
        for(int j=0;j<n;j++){
            if(grid[i][j] == '1'){
                cut++;
                grid[i][j] = '0';
                Queue<Integer> deque = new LinkedList<>();
                deque.add(i * n + j);
                Integer p;
                while(!deque.isEmpty()){
                    p = deque.remove();
                    int row = p / n;
                    int col = p % n;
                    // 向右
                    if(col + 1 < n && grid[row][col + 1] == '1'){
                        deque.add(row * n + col + 1);
                        grid[row][col + 1] = '0';
                    }
                    // 向下
                    if(row + 1 < m && grid[row + 1][col] == '1'){
                        deque.add((row + 1) * n + col);
                        grid[row + 1][col] = '0';
                    }
                    // 向左
                    if(col-1 >= 0 && grid[row][col - 1] == '1'){
                        deque.add(row * n + col - 1);
                        grid[row][col - 1] = '0';
                    }
                    // 向上
                    if(row - 1 >= 0 && grid[row-1][col] == '1'){
                        deque.add((row - 1) * n + col);
                        grid[row-1][col] = '0';
                    }
                }
            }
        }
    }
    return cut;
}

方法二:并查集,将相连的1加入同一个集合,最终查看有几个集合,岛屿的数量就是几个。

class Solution {
    class UnionFind {
        int count;
        int[] parent;
        int[] rank;
        public UnionFind(char[][] grid) {
            count = 0;
            int m = grid.length;
            int n = grid[0].length;
            parent = new int[m * n];
            rank = new int[m * n];
            for (int i = 0; i < m; ++i) {
                for (int j = 0; j < n; ++j) {
                    if (grid[i][j] == '1') {
                        parent[i * n + j] = i * n + j;
                        ++count;
                    }
                    rank[i * n + j] = 0;
                }
            }
        }
        public int find(int i) {
            if (parent[i] != i) parent[i] = find(parent[i]);
            return parent[i];
        }
        public void union(int x, int y) {
            int rootx = find(x);
            int rooty = find(y);
            if (rootx != rooty) {
                if (rank[rootx] > rank[rooty]) {
                    parent[rooty] = rootx;
                } else if (rank[rootx] < rank[rooty]) {
                    parent[rootx] = rooty;
                } else {
                    parent[rooty] = rootx;
                    rank[rootx] += 1;
                }
                --count;
            }
        }
        public int getCount() {
            return count;
        }
    }
    public int numIslands(char[][] grid) {
        if (grid == null || grid.length == 0) {
            return 0;
        }
        int nr = grid.length;
        int nc = grid[0].length;
        int num_islands = 0;
        UnionFind uf = new UnionFind(grid);
        for (int r = 0; r < nr; ++r) {
            for (int c = 0; c < nc; ++c) {
                if (grid[r][c] == '1') {
                    grid[r][c] = '0';
                    if (r - 1 >= 0 && grid[r-1][c] == '1') {
                        uf.union(r * nc + c, (r-1) * nc + c);
                    }
                    if (r + 1 < nr && grid[r+1][c] == '1') {
                        uf.union(r * nc + c, (r+1) * nc + c);
                    }
                    if (c - 1 >= 0 && grid[r][c-1] == '1') {
                        uf.union(r * nc + c, r * nc + c - 1);
                    }
                    if (c + 1 < nc && grid[r][c+1] == '1') {
                        uf.union(r * nc + c, r * nc + c + 1);
                    }
                }
            }
        }
        return uf.getCount();
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值