Java算法小练

题目要求:

一个整型数组里除了两个数字只出现一次,其他的数字都出现了两次。请写程序找出这两个只出现一次的数字。

数据范围:数组长度 2≤n≤10002≤n≤1000,数组中每个数的大小 0<val≤10000000<val≤1000000

要求:空间复杂度 O(1)O(1),时间复杂度 O(n)O(n)

提示:输出时按非降序排列。

思路分析

  1. 异或的性质:

任何数与自身的异或结果为 0,即 a ^ a = 0。

任何数与 0 的异或结果为其自身,即 a ^ 0 = a。

异或操作满足交换律和结合律。

  1. 步骤:

步骤一:对数组中的所有数字进行异或操作。由于数组中除了两个数字只出现一次,其他数字都出现了两次,所有成对的数字异或后会变成 0,最终的结果就是这两个只出现一次的数字的异或结果 xor = a ^ b。

步骤二:找到 xor 中任意一个为 1 的位。这个位表示 a 和 b 在这一位上不同。我们可以利用这个位将数组分成两组,一组在该位上为 0,另一组在该位上为 1。这样,a 和 b 会被分到不同的组中。

步骤三:分别对这两组进行异或操作,最终得到两个只出现一次的数字。

  1. 时间复杂度:

整个过程只需要遍历数组几次,因此时间复杂度为 O(n)。

  1. 空间复杂度:

只使用了常数级别的额外空间,因此空间复杂度为 O(1)。

Java 实现

java 复制代码
import java.util.Arrays;

public class FindTwoUniqueNumbers {
    public static int[] findTwoUniqueNumbers(int[] nums) {
        // 第一步:找到两个唯一数字的异或结果
        int xor = 0;
        for (int num : nums) {
            xor ^= num;
        }

        // 第二步:找到xor中任意一个为1的位
        // 这里我们找到最右边的1
        int diff = xor & (-xor);

        // 第三步:根据diff将数组分成两组,分别异或得到两个唯一数字
        int num1 = 0, num2 = 0;
        for (int num : nums) {
            if ((num & diff) == 0) {
                num1 ^= num;
            } else {
                num2 ^= num;
            }
        }

        // 返回结果时按非降序排列
        if (num1 < num2) {
            return new int[]{num1, num2};
        } else {
            return new int[]{num2, num1};
        }
    }

    public static void main(String[] args) {
        int[] nums = {4, 1, 2, 1, 2, 5};
        int[] result = findTwoUniqueNumbers(nums);
        System.out.println(Arrays.toString(result)); // 输出: [4, 5]
    }
}

代码讲解

  1. 第一步:

我们初始化一个变量 xor 为 0,然后遍历数组,将所有数字进行异或操作。由于成对的数字会相互抵消,最终 xor 的值就是两个唯一数字的异或结果。

  1. 第二步:

我们需要找到一个位,这个位在 xor 中为 1,表示 a 和 b 在这一位上不同。使用 xor & (xor) 可以找到 xor 中最右边的 1 位。

  1. 第三步:

根据找到的 diff 位,将数组分成两组:

一组是该位为 0 的数字。

另一组是该位为 1 的数字。

分别对这两组进行异或操作,最终得到两个唯一数字 num1 和 num2。

  1. 排序:

最后,根据题目要求,将结果按非降序排列。如果 num1 小于 num2,直接返回 [num1, num2],否则返回 [num2, num1]。

示例

对于输入数组 {4, 1, 2, 1, 2, 5}:

第一步:xor = 4 ^ 1 ^ 2 ^ 1 ^ 2 ^ 5 = 4 ^ 5 = 1。

第二步:diff = 1 & (1) = 1。

第三步:

第一组(该位为 0):4,4 ^ 4 = 0。

第二组(该位为 1):1, 2, 1, 2, 5,1 ^ 2 ^ 1 ^ 2 ^ 5 = 5。

最终结果:[4, 5]。

题目要求:

给定一个长度为n的数组arr,返回arr的最长无重复元素子数组的长度,无重复指的是所有数字都不相同。

子数组是连续的,比如[1,3,5,7,9]的子数组有[1,3],[3,5,7]等等,但是[1,3,7]不是子数组

思路分析:

  1. 滑动窗口的概念:

我们可以使用两个指针 left 和 right 来表示当前窗口的左右边界。

right 指针向右移动,尝试扩展窗口。

如果 arr[right] 在当前窗口中已经存在,我们需要移动 left 指针,直到窗口中不再包含重复元素。

  1. 使用数据结构记录元素出现的位置:

我们可以使用一个 HashMap 来记录每个元素最后出现的位置。

这样,当遇到一个重复元素时,我们可以快速地将 left 指针移动到该重复元素上一次出现位置的下一个位置。

  1. 维护最大长度:

在遍历过程中,我们始终维护一个变量 maxLength 来记录当前找到的最长无重复子数组的长度。

具体步骤

  1. 初始化:

创建一个空的 HashMap 来记录元素及其最后出现的位置。

初始化两个指针 left 和 right 为 0,以及 maxLength 为 0。

  1. 遍历数组:

对于每个元素 arr[right],检查它是否已经存在于 HashMap 中。

如果存在,说明遇到了重复元素,将 left 指针移动到 HashMap 中记录的位置的下一个位置。

更新 HashMap 中 arr[right] 的位置为 right。

计算当前窗口的长度 right left + 1,并更新 maxLength。

  1. 返回结果:

遍历完成后,maxLength 就是最长的无重复子数组的长度。

Java 实现

java 复制代码
import java.util.HashMap;

public class LongestSubarrayWithoutRepeats {
    public static int lengthOfLongestSubarray(int[] arr) {
        // HashMap 用于存储元素及其最后出现的位置
        HashMap<Integer, Integer> map = new HashMap<>();
        int maxLength = 0;
        int left = 0; // 左指针

        for (int right = 0; right < arr.length; right++) {
            // 如果当前元素已经在 HashMap 中,并且其位置在当前窗口内
            if (map.containsKey(arr[right]) && map.get(arr[right]) >= left) {
                // 将左指针移动到重复元素的下一个位置
                left = map.get(arr[right]) + 1;
            }
            // 更新元素的位置
            map.put(arr[right], right);
            // 更新最大长度
            maxLength = Math.max(maxLength, right - left + 1);
        }

        return maxLength;
    }

    public static void main(String[] args) {
        int[] arr = {1, 3, 5, 7, 9, 3, 5, 7, 11};
        int result = lengthOfLongestSubarray(arr);
        System.out.println("最长的无重复子数组的长度是: " + result); // 输出: 5
    }
}

代码讲解

  1. HashMap 的使用:

我们使用 HashMap<Integer, Integer> 来记录每个元素最后出现的位置。

例如,map.put(arr[right], right); 会在每次遇到一个元素时更新其在数组中的最新位置。

  1. 左指针的移动:

当我们遇到一个重复元素时,我们需要将 left 指针移动到该重复元素上一次出现位置的下一个位置。

这可以通过 left = map.get(arr[right]) + 1; 实现。

  1. 最大长度的更新:

在每次循环中,我们计算当前窗口的长度 right left + 1,并将其与 maxLength 进行比较,更新 maxLength 以记录最长的无重复子数组的长度。

  1. 示例:

对于数组 {1, 3, 5, 7, 9, 3, 5, 7, 11},最长的无重复子数组是 {1, 3, 5, 7, 9},长度为 5。

时间和空间复杂度

时间复杂度:O(n),其中 n 是数组的长度。每个元素最多被访问两次(一次被 right 指针访问,一次被 left 指针访问)。

空间复杂度:O(min(m, n)),其中 m 是元素的值域大小,n 是数组的长度。在最坏的情况下,HashMap 需要存储所有唯一的元素。

题目要求

三步问题。有个小孩正在上楼梯,楼梯有n阶台阶,小孩一次可以上1阶、2阶或3阶。实现一种方法,计算小孩有多少种上楼梯的方式。结果可能很大,你需要对结果模1000000007。

示例1:

输入:n=3

输出:4

说明:有四种走法

示例2:

输入:n=5

输出:13

思路分析

  1. 状态定义:

dp[i] 表示到达第 i 级台阶的方法数。

  1. 状态转移方程:

小孩一次可以上1阶、2阶或3阶,因此到达第 i 级台阶的方法数等于到达第 i1 级台阶的方法数加上到达第 i2 级台阶的方法数再加上到达第 i3 级台阶的方法数。

即:dp[i] = dp[i1] + dp[i2] + dp[i3]

  1. 初始化:

当 i = 0 时,只有一种方法,即不移动,所以 dp[0] = 1。

当 i = 1 时,只有一种方法,即上1阶,所以 dp[1] = 1。

当 i = 2 时,有两种方法,即上1阶+1阶或直接上2阶,所以 dp[2] = 2。

  1. 结果取模:

由于结果可能很大,我们需要对结果取模 1000000007。

Java 实现

java 复制代码
public class ClimbingStairs {
    public static int countWays(int n) {
        // 如果 n 小于等于 0,返回 0
        if (n <= 0) return 0;
        // 如果 n 等于 1,返回 1
        if (n == 1) return 1;
        // 如果 n 等于 2,返回 2
        if (n == 2) return 2;

        // 初始化 dp 数组
        int[] dp = new int[n + 1];
        dp[0] = 1; // 不移动
        dp[1] = 1; // 上1阶
        dp[2] = 2; // 上1阶+1阶 或 直接上2阶

        // 计算 dp[i]
        for (int i = 3; i <= n; i++) {
            dp[i] = (dp[i - 1] + dp[i - 2] + dp[i - 3]) % 1000000007;
        }

        return dp[n];
    }

    public static void main(String[] args) {
        int n = 5;
        int result = countWays(n);
        System.out.println("小孩有 " + result + " 种上楼的方法");
    }
}

代码讲解

  1. 初始化:

dp[0] = 1:表示不移动的方法数为1。

dp[1] = 1:表示上1阶的方法数为1。

dp[2] = 2:表示上1阶+1阶 或 直接上2阶的方法数为2。

  1. 动态规划计算:

从 i = 3 开始,利用状态转移方程 dp[i] = dp[i1] + dp[i2] + dp[i3] 计算 dp[i]。

对结果取模 1000000007,以防止结果过大。

  1. 输出结果:

返回 dp[n],即到达第 n 级台阶的方法数。

示例

当 n = 3 时,dp[3] = dp[2] + dp[1] + dp[0] = 2 + 1 + 1 = 4,输出4。

当 n = 5 时,dp[5] = dp[4] + dp[3] + dp[2] = 7 + 4 + 2 = 13,输出13。

相关推荐
良木林1 小时前
2024西游新生赛部分题解
c语言·数据结构·算法
宸码1 小时前
【机器学习】【无监督学习——聚类】从零开始掌握聚类分析:探索数据背后的隐藏模式与应用实例
人工智能·python·学习·算法·机器学习·数据挖掘·聚类
Kylin5241 小时前
C语言经典代码——part 30
c语言·开发语言·算法
又菜又爱玩的东哥2 小时前
字符串的常见操作【C语言】
c语言·开发语言·算法
Solitudefire2 小时前
蓝桥杯刷题——day1
java·算法·蓝桥杯
TT哇2 小时前
【每日一练 基础题】[蓝桥杯 2022 省 A] 求和
java·算法·蓝桥杯
奶油泡芙9312 小时前
Insert Digit插入数字
c++·算法
两袖清风9982 小时前
【算法】—— 前缀和
java·数据结构·算法
不烦下雨c3 小时前
【优选算法】二分算法(在排序数组中查找元素的第一个和最后一个位置,寻找峰值,寻找排序数组中的最小值)
算法
uhakadotcom3 小时前
杀疯了,90后博士第一次创业,刚成立1年半,融资12亿,估值30亿
后端·算法·架构