和数字相关算法题

移动零

给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。

>输入: [0,1,0,3,12]
>输出: [1,3,12,0,0]
class Solution {
    public void moveZeroes(int[] nums) {
        int idx = 0;
        for (int num : nums) {
            if (num != 0) nums[idx++] = num;
        }
        while (idx < nums.length) {
            nums[idx++] =0;
        }
    }
}

3. 接雨水(字节爱出)

给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。

>输入:height = [0,1,0,2,1,0,1,3,2,1,2,1]
>输出:6
>解释:上面是由数组 [0,1,0,2,1,0,1,3,2,1,2,1] 表示的高度图,在这种情况下,可以接 6 个单位的雨水(蓝色部分表示雨水)。
>输入:height = [4,2,0,3,2,5]
>输出:9
class Solution {
    public int trap(int[] height) {
        int min = 0, max = 0;
        int l = 0, r = height.length - 1;
        int res = 0;
        while(l < r) {
            min = height[height[l] < height[r] ? l++ : r--];
            max = Math.max(max, min);
            res += max - min;
        }
        return res;
    }
}

4. 下一个排列

实现获取 下一个排列 的函数,算法需要将给定数字序列重新排列成字典序中下一个更大的排列。

如果不存在下一个更大的排列,则将数字重新排列成最小的排列(即升序排列)。

必须 原地 修改,只允许使用额外常数空间。

>输入:nums = [1,2,3]
>输出:[1,3,2]
>输入:nums = [3,2,1]
>输出:[1,2,3]
>输入:nums = [1,1,5]
>输出:[1,5,1]
>输入:nums = [1]
>输出:[1]
    //源于离散数学及其应用的算法:(以3 4 5 2 1 为例)
    //从后往前寻找第一次出现的正序对:(找到 4,5)
    //之后因为从5 开始都是逆序,所以把他们反转就是正序:3 4 1 2 5
    //之后4 的位置应该是:在它之后的,比他大的最小值(5)
    //交换这两个值:得到 3 5 1 2 4
    // 对于初始即为逆序的序列,将在反转步骤直接完成
class Solution {
    public void nextPermutation(int[] nums) {
        int len = nums.length;
        if (len < 2) return;
        int i = len - 1;
        while (i > 0 && nums[i - 1] >= nums[i])
            i--; // 从后向前找第一个正序,这里最后i指向的是逆序起始位置
        reverse(nums, i, len - 1); // 翻转后面的逆序区域,使其变为正序
        if (i == 0) return;
        int j = i - 1;
        while(i < len && nums[j] >= nums[i])
            i++; // 找到第一个比nums[j]大的元素,交换即可
        // 交换
        swap(nums, i, j);
    }
    private void reverse(int[] nums, int i, int j) {
        while (i < j) {
            swap(nums, i++, j--);
        }
    }

    private void swap(int[] nums, int i, int j){
        int t = nums[i];
        nums[i] = nums[j];
        nums[j] = t;
    }
}

5. 孩子们的游戏

每年六一儿童节,牛客都会准备一些小礼物去看望孤儿院的小朋友,今年亦是如此。HF作为牛客的资深元老,自然也准备了一些小游戏。其中,有个游戏是这样的:首先,让小朋友们围成一个大圈。然后,他随机指定一个数m,让编号为0的小朋友开始报数。每次喊到m-1的那个小朋友要出列唱首歌,然后可以在礼品箱中任意的挑选礼物,并且不再回到圈中,从他的下一个小朋友开始,继续0…m-1报数….这样下去….直到剩下最后一个小朋友,可以不用表演,并且拿到牛客名贵的“名侦探柯南”典藏版(名额有限哦!!^_^)。请你试着想下,哪个小朋友会得到这份礼品呢?(注:小朋友的编号是从0到n-1)

如果没有小朋友,请返回-1

>输入
>5,3
>返回值
>3

约瑟夫环,圆圈长度为 n 的解可以看成长度为 n-1 的解再加上报数的长度 m。因为是圆圈,所以最后需要对 n 取余。

public int LastRemaining_Solution(int n, int m) {
    if (n == 0)     /* 特殊输入的处理 */
        return -1;
    if (n == 1)     /* 递归返回条件 */
        return 0;
    return (LastRemaining_Solution(n - 1, m) + m) % n;
}

165. 比较版本号

给你两个版本号 version1 和 version2 ,请你比较它们。

版本号由一个或多个修订号组成,各修订号由一个 ‘.’ 连接。每个修订号由 多位数字 组成,可能包含 前导零 。每个版本号至少包含一个字符。修订号从左到右编号,下标从 0 开始,最左边的修订号下标为 0 ,下一个修订号下标为 1 ,以此类推。例如,2.5.33 和 0.1 都是有效的版本号。

比较版本号时,请按从左到右的顺序依次比较它们的修订号。比较修订号时,只需比较 忽略任何前导零后的整数值 。也就是说,修订号 1 和修订号 001 相等 。如果版本号没有指定某个下标处的修订号,则该修订号视为 0 。例如,版本 1.0 小于版本 1.1 ,因为它们下标为 0 的修订号相同,而下标为 1 的修订号分别为 0 和 1 ,0 < 1 。

返回规则如下:

如果 version1 > version2 返回 1,
如果 version1 < version2 返回 -1,
除此之外返回 0。

>输入:version1 = "1.01", version2 = "1.001"
>输出:0
>解释:忽略前导零,"01""001" 都表示相同的整数 "1"
>输入:version1 = "1.0", version2 = "1.0.0"
>输出:0
>解释:version1 没有指定下标为 2 的修订号,即视为 "0"
>输入:version1 = "0.1", version2 = "1.1"
>输出:-1
>解释:version1 中下标为 0 的修订号是 "0",version2 中下标为 0 的修订号是 "1"0 < 1,所以 version1 < version2
>输入:version1 = "1.0.1", version2 = "1"
>输出:1
>输入:version1 = "7.5.2.4", version2 = "7.5.3"
>输出:-1
class Solution {
    public int compareVersion(String version1, String version2) {
        String[] a1 = version1.split("\\.");
        String[] a2 = version2.split("\\.");

        for (int n = 0; n < Math.max(a1.length, a2.length); n++) {
            int i = n < a1.length ? Integer.valueOf(a1[n]) : 0 ;
            int j = n < a2.length ? Integer.valueOf(a2[n]) : 0 ;
            if (i < j)
                return -1;
            else if (i > j)
                return 1;
            return 0;
        }
    }
}

209. 长度最小的子数组

给定一个含有 n 个正整数的数组和一个正整数 target 。

找出该数组中满足其和 ≥ target 的长度最小的 连续子数组 [numsl, numsl+1, …, numsr-1, numsr] ,并返回其长度。如果不存在符合条件的子数组,返回 0 。

>输入:target = 7, nums = [2,3,1,2,4,3]
>输出:2
>解释:子数组 [4,3] 是该条件下的长度最小的子数组。
>输入:target = 4, nums = [1,4,4]
>输出:1
>输入:target = 11, nums = [1,1,1,1,1,1,1,1]
>输出:0
class Solution {
    public int minSubArrayLen(int s, int[] nums) {
        // 滑动窗口
        int i = 0;
        int sum = 0;
        int len = 0;
        for (int j = 0; j < nums.length; j++){
            sum += nums[j];
            // 注意这个骚条件
            while (sum >= s){
                len = len == 0 ? j - i + 1 : Math.min(len, j - i + 1);
                // 滑动
                sum -= nums[i++];
            }
        }
        return len;
    }
}

628. 三个数的最大乘积

给你一个整型数组 nums ,在数组中找出由三个数组成的最大乘积,并输出这个乘积。

>输入:nums = [1,2,3]
>输出:6
>输入:nums = [1,2,3,4]
>输出:24
>输入:nums = [-1,-2,-3]
>输出:-6
class Solution {
    public int maximumProduct(int[] nums) {
        int max1 = Integer.MIN_VALUE, max2 = Integer.MIN_VALUE, max3 = Integer.MIN_VALUE,min1 = Integer.MAX_VALUE, min2 = Integer.MAX_VALUE;
        for (int n : nums) {
            if (n > max1) {
                max3 = max2;
                max2 = max1;
                max1 = n;
            } else if (n > max2) {
                max3 = max2;
                max2 = n;
            } else if (n > max3) {
                max3 = n;
            }

            if (n < min1) {
                min2 = min1;
                min1 = n;
            } else if (n < min2) {
                min2 = n;
            }
        }
        return Math.max(max1*max2*max3, max1*min1*min2);
    }
}

179. 最大数

给定一组非负整数 nums,重新排列它们每个数字的顺序(每个数字不可拆分)使之组成一个最大的整数。

注意:输出结果可能非常大,所以你需要返回一个字符串而不是整数。

>输入:nums = [10,2]
>输出:"210"
>输入:nums = [3,30,34,5,9]
>输出:"9534330"
>输入:nums = [1]
>输出:"1"
>输入:nums = [10]
>输出:"10"
class Solution {
    public String largestNumber(int[] nums) {
        if (nums == null || nums.length == 0)
            return "";
        int n = nums.length;
        String[] ss = new String[n];
        for (int i = 0; i < n; i++)
            ss[i] = nums[i] + "";
        Arrays.sort(ss, (a, b) -> (b + a).compareTo(a + b));
        StringBuilder sb = new StringBuilder();
        for (String s : ss)
            sb.append(s);
        String res = sb.charAt(0) == '0' ? "0" : sb.toString();
        return res;
    }
}

306. 累加数

累加数是一个字符串,组成它的数字可以形成累加序列。

一个有效的累加序列必须至少包含 3 个数。除了最开始的两个数以外,字符串中的其他数都等于它之前两个数相加的和。

给定一个只包含数字 ‘0’-‘9’ 的字符串,编写一个算法来判断给定输入是否是累加数。

说明: 累加序列里的数不会以 0 开头,所以不会出现 1, 2, 03 或者 1, 02, 3 的情况。

>输入: "112358"
>输出: true 
>解释: 累加序列为: 1, 1, 2, 3, 5, 8 。1 + 1 = 2, 1 + 2 = 3, 2 + 3 = 5, 3 + 5 = 8
>输入: "199100199"
>输出: true 
>解释: 累加序列为: 1, 99, 100, 199。1 + 99 = 100, 99 + 100 = 199
class Solution {
    public boolean isAdditiveNumber(String num) {
        return dfs(num, 0, 0, num.length(), 0, 0);
    }

    // num:原始字符串 idx:当前处理的下标 sum:上一层的两数和
    // len:字符串长度 pre:前一个数 k:当前已经处理数的个数
    private boolean dfs(String num, int idx, long sum, int len, long pre, int k) {
        // 达到末尾,判断是否存在至少3个数
        if (idx == len)
            return k > 2;
        // [idx, i] 代表当前尝试的数
        // 为了避免溢出long 使用结论:不存在符合条件的数,其长度超过原始字符串一半
        for (int i = idx + 1; i <= len && i - idx <= len / 2; i++) {
            // 剪枝:以0开头且不是单纯的0的数不符合题意
            if (i != idx + 1 && num.charAt(idx) == '0')
                return false;
            long cur = Long.parseLong(num.substring(idx, i));
            // 剪枝:校验两数和 不等于当前数
            if (k >= 2 && cur != sum)
                continue;
            // 继续dfs
            if (dfs(num, i, pre + cur, len, cur, k + 1))
                return true;
        }
        return false;
    }
}

402. 移掉K位数字

给定一个以字符串表示的非负整数 num,移除这个数中的 k 位数字,使得剩下的数字最小。

注意:

num 的长度小于 10002 且 ≥ k。
num 不会包含任何前导零。

>输入: num = "1432219", k = 3
>输出: "1219"
>解释: 移除掉三个数字 4, 3, 和 2 形成一个新的最小的数字 1219。
>输入: num = "10200", k = 1
>输出: "200"
>解释: 移掉首位的 1 剩下的数字为 200. 注意输出不能有任何前导零。
>输入: num = "10", k = 2
>输出: "0"
>解释: 从原数字移除所有的数字,剩余为空就是0。
class Solution {
    public String removeKdigits(String num, int k) {
        if (num.length() == k)
            return "0";
        StringBuilder s = new StringBuilder(num);
        for (int i = 0; i < k; i++) {
            int idx = 0;
            for (int j = 1; j < s.length() && s.charAt(j) >= s.charAt(j - 1); j++)
                idx = j;
            s.delete(idx, idx + 1);
            while (s.length() > 1 && s.charAt(0) == '0')
                s.delete(0, 1);
        }
        return s.toString();
    }
}

求一个数的平方根,精度0.01

public static void main(String[] args) {
    int a = 5;
    double l = 0, r = a;
    while (l <= r) {
        double m = l + (r - l) / 2;
        double val = m * m - a;
        if (Math.abs(val) <= 0.01) {
            System.out.println(m);
            return;
        }
        else if (val > 0.01)
            r = m;
        else
            l = m;
    }
}

本博客所有文章除特别声明外,大部分为学习心得,欢迎与博主联系讨论