十七 技巧

题目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.length1 <= 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,原地 对它们进行排序,使得相同颜色的元素相邻,并按照红色、白色、蓝色顺序排列。我们使用整数
0、1和2分别表示红色、白色和蓝色。必须在不使用库内置的 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.length1 <= n <= 300nums[i]为0、1或2
思路:
排序算法最少都是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 <= 1000 <= nums[i] <= 100
思路:背背背
算法推导
如何得到这样的排列顺序?这是本文的重点。我们可以这样来分析:
我们希望下一个数 比当前数大,这样才满足 "下一个排列" 的定义。因此只需要 将后面的「大数」与前面的「小数」交换,就能得到一个更大的数。比如 123456,将 5 和 6 交换就能得到一个更大的数 123465。
我们还希望下一个数 增加的幅度尽可能的小,这样才满足"下一个排列与当前排列紧邻"的要求。为了满足这个要求,我们需要:
在 尽可能靠右的低位 进行交换,需要 从后向前 查找
将一个 尽可能小的「大数」 与前面的「小数」交换。比如 123465,下一个排列应该把 5 和 4 交换而不是把 6 和 4 交换
将「大数」换到前面后,需要将「大数」后面的所有数 重置为升序,升序排列就是最小的排列。以 123465 为例:首先按照上一步,交换 5 和 4,得到 123564;然后需要将 5 之后的数重置为升序,得到 123546。显然 123546 比 123564 更小,123546 就是 123465 的下一个排列
以上就是求 "下一个排列" 的分析过程。
算法过程
标准的 "下一个排列" 算法可以描述为:
从后向前 查找第一个 相邻升序 的元素对 (i,j),满足 A[i] < A[j]。此时 [j,end) 必然是降序
在 [j,end) 从后向前 查找第一个满足 A[i] < A[k] 的 k。A[i]、A[k] 分别就是上文所说的「小数」、「大数」
将 A[i] 与 A[k] 交换
可以断定这时 [j,end) 必然是降序,逆置 [j,end),使其升序
如果在步骤 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]范围内(包括1和n),可知至少存在一个重复的整数。假设
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 <= 105nums.length == n + 11 <= nums[i] <= nnums中 只有一个整数 出现 两次或多次 ,其余整数均只出现 一次进阶:
- 如何证明
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;
}
}