算法总结——【技巧,ACM模式输入】

十七 技巧

题目1------只出现一次的数字【36】

给你一个 非空 整数数组 nums ,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。

你必须设计并实现线性时间复杂度的算法来解决此问题,且该算法只使用常量额外空间。

示例 1 :

**输入:**nums = 2,2,1

**输出:**1

示例 2 :

**输入:**nums = 4,1,2,1,2

**输出:**4

示例 3 :

**输入:**nums = 1

**输出:**1

提示:

  • 1 <= nums.length <= 3 * 104
  • -3 * 104 <= nums[i] <= 3 * 104
  • 除了某个元素只出现一次以外,其余每个元素均出现两次。

异或运算,a^a=0,a^0=a

java 复制代码
public int singleNumber(int[] nums) {
    int ans = 0;
    for (int num : nums) {
        ans ^= num;
    }
    return ans;
}

题目2------多数元素【67】

给定一个大小为 n 的数组 nums ,返回其中的多数元素。多数元素是指在数组中出现次数 大于 ⌊ n/2 ⌋ 的元素。

你可以假设数组是非空的,并且给定的数组总是存在多数元素。

示例 1:

复制代码
输入:nums = [3,2,3]
输出:3

示例 2:

复制代码
输入:nums = [2,2,1,1,1,2,2]
输出:2

提示:

  • n == nums.length
  • 1 <= n <= 5 * 104
  • -109 <= nums[i] <= 109
  • 输入保证数组中一定有一个多数元素。

**进阶:**尝试设计时间复杂度为 O(n)、空间复杂度为 O(1) 的算法解决此问题。

思路:哈希,排序都不符合要求,摩尔投票法

摩尔投票:

java 复制代码
public int majorityElement(int[] nums) {
    int c = 0;
    int vote = 0;
    for (int num : nums) {
        if (vote == 0) {
            c = num;
        }
        if (c == num) {
            vote++;
        } else {
            vote--;
        }
    }
    return c;
}

题目3------颜色分类

给定一个包含红色、白色和蓝色、共 n 个元素的数组 nums原地 对它们进行排序,使得相同颜色的元素相邻,并按照红色、白色、蓝色顺序排列。

我们使用整数 012 分别表示红色、白色和蓝色。

必须在不使用库内置的 sort 函数的情况下解决这个问题。

示例 1:

复制代码
输入:nums = [2,0,2,1,1,0]
输出:[0,0,1,1,2,2]

示例 2:

复制代码
输入:nums = [2,0,1]
输出:[0,1,2]

提示:

  • n == nums.length
  • 1 <= n <= 300
  • nums[i]012

思路:

排序算法最少都是O(nlogn),技术排序

一次遍历区统计2的个数并且计算数组总和,这样可以计算出1和0的个数

然后再遍历一次,

java 复制代码
 public void sortColors(int[] nums) {
     int count2 = 0;
     int sum = 0;
     for (int num : nums) {
         if (num==2) count2++;
         sum += num;
     }
     int count1 = sum - count2*2;
     int count0 = nums.length - count1 - count2;
     for (int i = 0; i < nums.length; i++) {
         if (count0 != 0) {
             nums[i] = 0;
             count0--;
         } else if (count1 != 0) {
             nums[i] = 1;
             count1--;
         } else {
             nums[i] = 2;
             count2--;
         }
     }
 }

题目4------下一个排列【133】

高频背

整数数组的一个 排列 就是将其所有成员以序列或线性顺序排列。

  • 例如,arr = [1,2,3] ,以下这些都可以视作 arr 的排列:[1,2,3][1,3,2][3,1,2][2,3,1]

整数数组的 下一个排列 是指其整数的下一个字典序更大的排列。更正式地,如果数组的所有排列根据其字典顺序从小到大排列在一个容器中,那么数组的 下一个排列 就是在这个有序容器中排在它后面的那个排列。如果不存在下一个更大的排列,那么这个数组必须重排为字典序最小的排列(即,其元素按升序排列)。

  • 例如,arr = [1,2,3] 的下一个排列是 [1,3,2]
  • 类似地,arr = [2,3,1] 的下一个排列是 [3,1,2]
  • arr = [3,2,1] 的下一个排列是 [1,2,3] ,因为 [3,2,1] 不存在一个字典序更大的排列。

给你一个整数数组 nums ,找出 nums 的下一个排列。

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

示例 1:

复制代码
输入:nums = [1,2,3]
输出:[1,3,2]

示例 2:

复制代码
输入:nums = [3,2,1]
输出:[1,2,3]

示例 3:

复制代码
输入:nums = [1,1,5]
输出:[1,5,1]

提示:

  • 1 <= nums.length <= 100
  • 0 <= nums[i] <= 100

思路:背背背

算法推导

如何得到这样的排列顺序?这是本文的重点。我们可以这样来分析:

  1. 我们希望下一个数 比当前数大,这样才满足 "下一个排列" 的定义。因此只需要 将后面的「大数」与前面的「小数」交换,就能得到一个更大的数。比如 123456,将 5 和 6 交换就能得到一个更大的数 123465。

  2. 我们还希望下一个数 增加的幅度尽可能的小,这样才满足"下一个排列与当前排列紧邻"的要求。为了满足这个要求,我们需要:

  3. 在 尽可能靠右的低位 进行交换,需要 从后向前 查找

  4. 将一个 尽可能小的「大数」 与前面的「小数」交换。比如 123465,下一个排列应该把 5 和 4 交换而不是把 6 和 4 交换

  5. 将「大数」换到前面后,需要将「大数」后面的所有数 重置为升序,升序排列就是最小的排列。以 123465 为例:首先按照上一步,交换 5 和 4,得到 123564;然后需要将 5 之后的数重置为升序,得到 123546。显然 123546 比 123564 更小,123546 就是 123465 的下一个排列

以上就是求 "下一个排列" 的分析过程。

算法过程

标准的 "下一个排列" 算法可以描述为:

  1. 从后向前 查找第一个 相邻升序 的元素对 (i,j),满足 Ai < Aj。此时 [j,end) 必然是降序

  2. j,end) 从后向前 查找第一个满足 A\[i < Ak 的 k。Ai、Ak 分别就是上文所说的「小数」、「大数」

  3. 将 Ai 与 Ak 交换

  4. 可以断定这时 [j,end) 必然是降序,逆置 [j,end),使其升序

  5. 如果在步骤 1 找不到符合的相邻元素对,说明当前 [begin,end) 为一个降序顺序,则直接跳到步骤 4

java 复制代码
public void nextPermutation(int[] nums) {
    if (nums.length < 2) {
        return;
    }
    // 1.从后往前找第一个升序峰
    int i = nums.length-2, j = nums.length-1;
    // 一定要严格大于
    while (i >= 0 && nums[i] >= nums[j]) {
        i--;
        j--;
    }
    // 2.找第一个nums[i]<nums[k]
    int k = nums.length - 1;
    if (i>=0){
        while (k>=0 && nums[i ]>= nums[k]) k--;
        swap(nums, i, k);
    }


    reverse(nums, j, nums.length - 1);
}

public void swap(int[] nums, int i, int j) {
    int temp = nums[i];
    nums[i] = nums[j];
    nums[j] = temp;
}

public void reverse(int[] nums, int left, int right) {
    while (left < right) {
        swap(nums, left, right);
        left++;
        right--;
    }
}


public static void main(String[] args) {
    Main main = new Main();
    int[] nums = new int[]{1,2,3};
    main.nextPermutation(nums);
    for (int num : nums) {
        System.out.println(num);
    }
}

题目5------寻找重复数【35】

【鸽笼定理】可能考察口述

给定一个包含 n + 1 个整数的数组 nums ,其数字都在 [1, n] 范围内(包括 1n),可知至少存在一个重复的整数。

假设 nums 只有 一个重复的整数 ,返回 这个重复的数

你设计的解决方案必须 不修改 数组 nums 且只用常量级 O(1) 的额外空间。

示例 1:

复制代码
输入:nums = [1,3,4,2,2]
输出:2

示例 2:

复制代码
输入:nums = [3,1,3,4,2]
输出:3

示例 3 :

复制代码
输入:nums = [3,3,3,3,3]
输出:3

提示:

  • 1 <= n <= 105
  • nums.length == n + 1
  • 1 <= nums[i] <= n
  • nums只有一个整数 出现 两次或多次 ,其余整数均只出现 一次

进阶:

  • 如何证明 nums 中至少存在一个重复的数字?
  • 你可以设计一个线性级时间复杂度 O(n) 的解决方案吗?

鸽笼定理,可以使用

快慢指针、将数组和索引的转化成链表就变成了,判断链表的环

java 复制代码
    public int findDuplicate(int[] nums) {
        int slow = 0, fast = 0;
        do {
            slow = nums[slow];
            fast = nums[nums[fast]];
        } while (slow != fast);
        slow = 0;
        while (slow != fast) {
            slow = nums[slow];
            fast = nums[fast];
        }
        return slow;
    }

十八 ACM数据读入

1 从控制台读取输入

两数之和为例,它的输入是这样的:

csharp 复制代码
[2,7,11,15]
9

我们需要读入第一行的字符串和第二行的整数,然后将字符串转换成数组。首先,使用 Scanner 进行逐行读取。

Java 复制代码
import java.util.Scanner;
   
public class Main {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        String line = s.nextLine();         //读取字符串
        System.out.println("字符串:"line);
        int a = s.nextInt();                //读取整数
        System.out.println("整数:"+a);
    }
}

2 手动构建树结构

从控制台读取字符串之后,需要手动将这些字符串转换成数组、链表和二叉树,转化的代码都是很固定的,可以作为模板使用。

数组

数组的构建非常简单,将字符串根据分隔符分割,然后转换成整型即可。代码如下:

Java 复制代码
    private static int[] StringToIntArray(String str) {
        String[] parts = str                //根据","分割成字符串数组
            .substring(1, str.length() - 2)
            .split(",");
        int[] nums = new int[parts.length];
        for (int i = 0; i < parts.length; i ++) 
            nums[i] = Integer.parseInt(parts[i]);    //转为整型数组
        
        return nums;
    }
链表

链表的构建也不难。

Java 复制代码
    private static ListNode StringToListNode(String str) {
        String[] parts = str    //分割字符串
            .substring(1, str.length() - 2)
            .split(",");

        ListNode dummy = new ListNode(-1), cur = dummy;        //虚拟头结点

        for (int s: parts) {
            cur.next = new ListNode(Integer.parseInt(s));      //逐个添加链表的结点
            cur = cur.next;
        }

        return dummy.next;    //返回真正的头结点
    }
二叉树

构建二叉树的代码就很长了,个人感觉手动构建二叉树相当于一道中等题了。

Java 复制代码
import java.util.LinkedList;
import java.util.Queue;
import java.util.Scanner;

public class Main {

    private static class TreeNode {    //二叉树类
        int val;
        TreeNode left, right;
        TreeNode() {}
        TreeNode(int val) { this.val = val; }
    }

    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        String str = scanner.nextLine();

        TreeNode root = StringToTreeNode(str);
    }

    private static TreeNode StringToTreeNode(String str) {
        String[] parts = str
            .substring(1, str.length() - 1)
            .split(",");

        String item = parts[0];
        TreeNode root = new TreeNode(Integer.parseInt(item));    //二叉树的根节点

        Queue<TreeNode> q = new LinkedList<>();    //使用队列添加结点
        q.offer(root);

        int index = 1;
        while (!q.isEmpty()) {
            TreeNode node = q.poll();

            if (index == parts.length) break;

            item = parts[index ++];
            if (!item.equals("null")) {
                node.left = new TreeNode(Integer.parseInt(item));
                q.offer(node.left);
            }

            if (index == parts.length) break;

            item = parts[index ++];
            if (!item.equals("null")) {
                node.right = new TreeNode(Integer.parseInt(item));
                q.offer(node.right);
            }
        }

        return root;
    }
}
相关推荐
江畔柳前堤8 小时前
大语言模型分布式训练:从并行策略到万卡工程的系统梳理
人工智能·分布式·深度学习·算法·目标检测·机器学习·语言模型
Forever Nore9 小时前
LeetCode 4 寻找两个正序数组的中位数 - 二分
算法·leetcode
罗西的思考11 小时前
【OpenClaw具身硬件】MiniClaw 阅读笔记---(1)基础
人工智能·算法·机器学习
蛋先生DX11 小时前
大模型参数存储格式揭秘:BF不是男朋友
深度学习·算法·llm
爱跳舞的烤冷面12 小时前
自学嵌入式第22天(数据结构——哈希)
数据结构·算法·哈希算法
猎嘤一号12 小时前
博弈论(Game Theory)的理论、算法与工程
人工智能·算法·安全·博弈论
月光船幽幽12 小时前
影子模式下保护 logits 不被修改
人工智能·python·算法
马拉AI12 小时前
腾讯开源 Agent 记忆系统,AI“换对话就忘”的问题有了新解法(附安装使用教程)
人工智能·算法·开源·科研
地平线开发者14 小时前
征程6工具链模型X86推理方式说明
算法
地平线开发者14 小时前
【征程6】校准量化中HistogramObserver解析
算法