关于数组的题目
以下采用【Java的解法】
参考了LeetCode的题解
题目26: 删除排序数组中的重复项
- 我的解答:
// 目的是循环一次,删除重复的元素,返回新的列表(无重复元素的)的长度
class Solution {
public int removeDuplicates(int[] nums) {
if(nums.length==0) return 0;
else if(nums.length==1) return 1;
else{
int n=1; //用来记录长度
int l=0; //双指针
for(int r=1;r<nums.length;r++){
if(nums[r]!=nums[l]){
l++;
nums[l]=nums[r];
n++;
}
}
return n;
}
}
}
- 官方解答:
- 解法: 双指针
题目48: 旋转图像
- 思路1: 先对矩阵进行转置,然后一头一尾两列两列交换。
class Solution {
public void rotate(int[][] matrix) {
//对矩阵进行转置,这里假设n>=1,实际上我们对角线元素不需要考虑转置
int n=matrix.length;
int tmp;
for(int i=0;i<n;i++){
//注意这里是从i+1开始
for(int j=i+1;j<n;j++){
tmp=matrix[i][j];
matrix[i][j]=matrix[j][i];
matrix[j][i]=tmp;
}
}
//一头一尾两列两列交换
for(int i=0,j=n-1;i<j;i++,j--){
for(int l=0;l<n;l++){
tmp=matrix[l][i];
matrix[l][i]=matrix[l][j];
matrix[l][j]=tmp;
}
}
}
}
- 思路2: 找规律
关键在于确定循环的边界,以及交换的顺序。
class Solution {
public void rotate(int[][] matrix) {
int n=matrix.length;
for(int i=0;i<n/2;i++){
for(int j=i;j<n-1-i;j++){
//对选出来的四个数进行交换,这样也就可以轻松改变旋转的方向
int tmp=matrix[i][j];
matrix[i][j]=matrix[n-1-j][i];
matrix[n-1-j][i]=matrix[n-1-i][n-1-j];
matrix[n-1-i][n-1-j]=matrix[j][n-1-i];
matrix[j][n-1-i]=tmp;
}
}
}
}
题目1: 两数之和
- 收获:
- 小心数据相等的情况!
- ⚠️ 因为如果只有在if中return会编译不通过,所以可以在外部
throw new IllegalArgumentException("No two sum solution");
- ⚠️可以不需要构造
int tmp
,直接通过return new int[]{i,j}
。 - 哈希表
- 方法一:暴力法
class Solution {
public int[] twoSum(int[] nums, int target) {
for (int i = 0; i < nums.length; i++) {
for (int j = i + 1; j < nums.length; j++) {
if (nums[j] == target - nums[i]) {
return new int[] { i, j };
}
}
}
throw new IllegalArgumentException("No two sum solution");
}
}
- 时间复杂度: O(n^2)
- 空间复杂度: O(1)
- 方法二:排序+双指针
哈希表
- 方法三:哈希表
具体思路大致是通过将每个元素的Key,Value映射到一个数,然后把这个数当作是它自己的编码。所以查找的时候可以只查找1次就可以直到所查找的数字是否在里面。
元素序号 | 1 | 2 |
---|---|---|
Key | 2 | 3 |
Value | 0 | 1 |
Hash表在Java中的使用:
.put(KEY,VALUE)
,.get(KEY)
,.containsKey(KEY)
,.getOrDefault(...,...)
。
import java.util.HashMap;
import java.util.Map;
Map<Integer,String> map=new HashMap<Integer,String>();
map.put(1,"hi");
if(map.containsKey(1)) //通过这个来查找
System.out.println(map.get(1));
System.out.println(map.getOrDefault(1,"Don't contain the input key")); //如果存在input的key则输出对应的值
System.out.println(map.getOrDefault(3,"Don't contain the input key")); //否则输出后面自定义的值,因为这里的map是从integer到String所以填的是字符串。
此题Hash解法:
class Solution {
public int[] twoSum(int[] nums, int target) {
Map<Integer, Integer> map = new HashMap<>();
for (int i = 0; i < nums.length; i++) {
int complement = target - nums[i];
if (map.containsKey(complement)) {
return new int[] { map.get(complement), i };
//因为之前的元素在map里,所以这里的顺序是i在后面
}
//如果要找的元素不在里面,就把当前元素的值当作key,index作为值存储起来
map.put(nums[i], i);
}
throw new IllegalArgumentException("No two sum solution");
}
}
- 时间复杂度: O(n),因为要对n个元素进行查找,每次查找的开销为O(1)
- 空间复杂度: O(n),因为该表最多存储n个元素
题目36: 有效的数独
- 我的思路:
- 直接开三个数组用来存储数据
class Solution {
public boolean isValidSudoku(char[][] board) {
int[][] h = new int[9][9]; // 用来记录每列中1-9是否出现过,第一个数字表示第几列
int[][] v = new int[9][9];
int[][] s = new int[9][9];
for (int i = 0; i < 9; i++) {
for (int j = 0; j < 9; j++) {
if (board[i][j] != '.') {
int k = board[i][j] - '0';
if (h[i][k - 1] == 0)
h[i][k - 1] += 1;
else
return false;
if (v[j][k - 1] == 0)
v[j][k - 1] += 1;
else
return false;
if (s[3 * (i / 3) + j / 3 ][k - 1] == 0)
s[3 * (i / 3) + j / 3 ][k - 1] += 1;
else
return false;
}
}
}
return true;
}
}
题目66:加一
- 收获:
- 如果直接初始化
int [] tmp=new int[10];
则tmp中的所有元素均为0。
- 如果直接初始化
- 别人的题解:
class Solution {
public int[] plusOne(int[] digits) {
for (int i = digits.length - 1; i >= 0; i--) {
digits[i]++;
digits[i] = digits[i] % 10;
if (digits[i] != 0) return digits;
}
digits = new int[digits.length + 1];
digits[0] = 1;
return digits;
}
}
- 因为这道题目巧在只可能在99,999等情况下才需要扩大数组的长度。其他情况下加完(即包括进位之后)函数的值都不为0。
题目283:移动零
- 收获:
- 双指针
- 比较慢的解法:
public void moveZeroes(int[] nums) {
int l = nums.length;
int k =0;
int[] tmp = new int[l]; //刚好用上初始化的默认填充元素是0
for(int i=0;i<l;i++) {
if(nums[i]!=0) {
tmp[k]=nums[i];
k++;}
}
for(int i=0;i<l;i++) {
nums[i]=tmp[i];
}
}
- 其他人优化的解法:双指针
//我根据我的理解码的
public void moveZeroes(int[] nums) {
int l=nums.length;
int j=0;
int i,tmp;
//j指向第一个0元素,换言之j记录了列表中非0元素的个数
while(j<l){
if(nums[j]==0) {
//寻找第一个非0元素
for(i=j+1;i<l;i++) {
if(nums[i]!=0) {
tmp=nums[i];
nums[i]=nums[j];
nums[j]=tmp;
break;
}
}
//如果i走到尽头了,则把剩下元素补充成为0
if(i==l) {
for(;j<l;j++)
nums[j]=0;
}
}
j++;
}
- 其实这个while可以去掉:
//https://leetcode-cn.com/problems/move-zeroes/solution/dong-hua-yan-shi-283yi-dong-ling-by-wang_ni_ma/
//一次遍历的解法,用i和j两个指针。
class Solution {
public void moveZeroes(int[] nums) {
if(nums == null) return;
//指针j,遍历开始后,在遇到0元素之前,随i同步移动,自从遇到第一个0元素,永远代表数组内从左到右的第一个0元素
int j = 0;
for(int i=0; i<nums.length; i++){
//指针i,从左到右逐个遍历,若不为0,与nums[j++]交换
if(nums[i] != 0){
int temp = nums[i];
nums[i] = nums[j];
nums[j++] = temp;
}
}
}
}
⚠️ 题目136:只出现一次的数字
- 收获:
- 哈希集
- 异或运算
哈希集
有两种不同类型的哈希表:哈希集合和哈希映射:
- 哈希集合是集合数据结构的实现之一,用于存储非重复值。
- 哈希映射是映射数据结构的实现之一,用于存储(key, value)键值对。
哈希表的关键思想是使用哈希函数将键映射到存储桶。
例子:
我们使用 y = x % 5 作为哈希函数。让我们使用这个例子来完成插入和搜索策略:
- 插入:我们通过哈希函数解析键,将它们映射到相应的桶中。例如,1987 分配给桶 2,而 24 分配给桶 4。
- 搜索:我们通过相同的哈希函数解析键,并仅在特定存储桶中搜索。
- 如果我们搜索1987,我们将使用相同的哈希函数将1987 映射到 2。因此我们在桶 2 中搜索,我们在那个桶中成功找到了 1987。
- 例如,如果我们搜索 23,将映射 23 到 3,并在桶 3 中搜索。我们发现 23 不在桶 3 中,这意味着 23 不在哈希表中。
哈希集在Java中的使用
import java.util.HashSet;
import java.util.Set;
Set<Integer> set = new HashSet<>(); //创建一个只存储Int的HashSet
set.add(1);
set.add(2);
set.add(3);
TC.println(set.remove(3));
TC.println(set.remove(10)); //如果移除失败就会返回false
//对于HashSet的遍历:但不直到为什么这里每次值会变动...
Iterator<Integer> i = set.iterator();
while(i.hasNext()) {
TC.println(i.next());
}
// 输出是 1,2
- 思路一:HashSet
public int singleNumber(int[] nums) {
Set<Integer> set = new HashSet<Integer>();
int l = nums.length;
for(int i=0;i<l;i++) {
if(!set.remove(nums[i]))
set.add(nums[i]);
}
//因为最后只剩下唯一一个元素了,即i.next();
return set.iterator().next();
}
- 线性时间复杂度 O(n)
- 空间复杂度上会达到 O(n),因为使用 Hash 映射来进行计算,遍历一次后结束得到结果,
异或运算(XOR)
异或运算的一些性质
- a ⊕ 0 = a a\oplus0=a a⊕0=a
- a ⊕ a = 0 a\oplus a=0 a⊕a=0
- XOR运算满足交换率和结合律: a ⊕ b ⊕ a = a ⊕ a ⊕ b = 0 ⊕ b = b a\oplus b \oplus a = a \oplus a \oplus b =0 \oplus b=b a⊕b⊕a=a⊕a⊕b=0⊕b=b
一个实际的例子
- 其实类似列竖式,但计算规则是:
1 ⊕ 1 = 0 1 \oplus 1 = 0 1⊕1=0
1 ⊕ 0 = 1 1 \oplus 0 = 1 1⊕0=1
0 ⊕ 1 = 1 0 \oplus 1 = 1 0⊕1=1
0 ⊕ 0 = 0 0 \oplus 0 = 0 0⊕0=0
但没有进位。
取
a
=
10
=
101
0
(
2
)
a = 10 = 1010_{(2)}
a=10=1010(2),
b
=
7
=
011
1
(
2
)
b=7=0111_{(2)}
b=7=0111(2)
a
⊕
0
=
101
0
(
2
)
⊕
000
0
(
2
)
=
101
0
(
2
)
=
a
a \oplus 0=1010_{(2)} \oplus 0000_{(2)} =1010_{(2)}=a
a⊕0=1010(2)⊕0000(2)=1010(2)=a
a
⊕
a
=
101
0
(
2
)
⊕
101
0
(
2
)
=
000
0
(
2
)
=
a
a \oplus a=1010_{(2)} \oplus 1010_{(2)} =0000_{(2)}=a
a⊕a=1010(2)⊕1010(2)=0000(2)=a
a
⊕
b
⊕
a
=
101
0
(
2
)
⊕
011
1
(
2
)
⊕
101
0
(
2
)
=
011
1
(
2
)
=
b
a \oplus b \oplus a = 1010_{(2)} \oplus 0111_{(2)} \oplus 1010_{(2)} = 0111_{(2)} = b
a⊕b⊕a=1010(2)⊕0111(2)⊕1010(2)=0111(2)=b
- 思路二:异或运算(XOR)
由前面的性质,这里很凑巧,所有重复的元素只重复两次,所以我们可通过异或运算将这些重复的元素变成0。也正因此,所有元素异或下来之后的结果就是最终的答案。
public int singleNumber(int[] nums) {
int l = nums.length;
int s = 0; //因为s跟任何元素的异或运算最后的值都是另一个元素
for(int i=0;i<l;i++) {
s^=nums[i];
}
return s;
}
⚠️ 题目189: 旋转数组 (Permutation的解法)
- 收获:
- permutation的写法:
- 思路一:根据数学观察得到的结果:
- 思路二:
public static void rotate(int[] nums, int k) {
int n = nums.length;
k %= n; //确保置换的位数小于数组长度,否则后面的reverse就会出现负数的情况ß
reverse(nums,0,n-1); //倒着输出数组nums[0,n-1]
reverse(nums,0,k-1); //因为第一次倒换最后的元素放到了前面。
reverse(nums,k,n-1);
}
public static void reverse(int[] nums, int l, int r) {
//通过对称两两交换实现倒着输出的目的
while(l<r) {
int tmp=nums[l];
nums[l]=nums[r];
nums[r]=tmp;
l++;
r--;
}
}
题目350: 两个数组的交集 II
- 收获:
- 利用Java的包
java.util.Arrays
- 排序:
import java.util.Arrays; int[] a={1,4,-1,5,0}; Arrays.sort(a); //直接在a原地修改。 //数组a[]的内容变为{-1,0,1,4,5}
- 对数组进行切片:
Arrays.copyOfRange(a, 0, 3); //以tableau的形式返回a中前3个元素
- 利用Java的包
- 思路一:排序+双指针:
public int[] intersect(int[] nums1, int[] nums2) {
Arrays.sort(nums1);
Arrays.sort(nums2);
int l1 = nums1.length, l2 = nums2.length;
int[] tmp = new int[Math.min(l1, l2)];
int i = 0, j = 0, k = 0;
while (i < l1 && j < l2) {
if (nums1[i] < nums2[j])
i++;
else if (nums2[j] < nums1[i])
j++;
else {
tmp[k++] = nums1[i];
i++;
j++;
}
}
int[] res = new int[k];
for(i=0;i<k;i++) {
res[i]=tmp[i];
}
return res;
}
- 思路二:HashMap
public int[] intersect(int[] nums1, int[] nums2) {
if(nums1.length>nums2.length)
return intersect(nums2,nums1); //为了保证节省空间
else {
//对含有较少元素的数组构建HashMap,Key是nums列表中元素,value是对应的值(这里是对应元素出现的次数)。
HashMap<Integer,Integer> m = new HashMap<Integer,Integer>();
for(int num:nums1) {
int cnt = m.getOrDefault(num,0)+1; //如果之前在map中有这个元素,则取之前的值+1,反之,若之前没有即为0+1。
m.put(num, cnt);
}
// 构建最后返回的数组
int[] res = new int[nums1.length];
// 遍历nums2
int k=0;
for(int num:nums2) {
//如果map中含有这个元素
if(m.getOrDefault(num,0)>0) {
res[k++]=num;
m.put(num,m.get(num)-1);
}
}
return Arrays.copyOfRange(res, 0, k);
}
}
题目:217. 存在重复元素
- 思路一:HashMap
public static boolean containsDuplicate(int[] nums) {
HashMap<Integer,Integer> m = new HashMap<Integer,Integer>();
for(int num:nums){
if(!m.containsKey(num))
m.put(num,1);
else
return true;
}
return false;
}
- 时间复杂度 : O(n)。 search() 和 insert() 各自使用 n 次,每个操作耗费常数时间。
- 空间复杂度 : O(n)。哈希表占用的空间与元素数量是线性关系。
- 思路二:排序
- 时间复杂度 : O(nlogn)。 排序的复杂度是 O(nlogn),扫描的复杂度是 O(n)。整个算法主要由排序过程决定,因此是 O(nlogn)。
- 空间复杂度 : O(1)。 这取决于具体的排序算法实现,通常而言,使用 堆排序 的话,是 O(1)。
题目:122. 买卖股票的最佳时机 II
- 收获:
java.util.ArrayList
的使用
import java.util.ArrayList;
ArrayList<Integer> list = new ArrayList<Integer>(); //链表的使用
list.add(4);
list.add(5);
list.add(9);
System.out.println(list.size());
System.out.println(list.get(2));
for(int num:list) {
System.out.println(num);
}
- 思路:看转折点
- 实际上只要看总共上升的值即可(贪心)每次只加增长的。
public int maxProfit(int[] prices) {
int res=0;
int l=prices.length;
for(int i=0;i<l-1;i++) {
if(prices[i+1]>prices[i])
res+=prices[i+1]-prices[i];
}
return res;
}