数组中重复的数字

剑指 Offer 03. 数组中重复的数字 - 力扣(Leetcode)

题目要求

找出数组中重复的数字。

在一个长度为 n 的数组 nums 里的所有数字都在 0~n-1 的范围内。数组中某些数字是重复的,但不知道有几个数字重复了,也不知道每个数字重复了几次。请找出数组中任意一个重复的数字。

例子:

1
2
3
输入:
[2, 3, 1, 0, 2, 5, 3]
输出:23

限制

2 <= n <= 100000

思路分析

解题方法

  1. 排序法;时间O(nlogn),空间O(1)
  2. 哈希表;时间O(n),空间O(n)
  3. 下标法(最优);时间O(n),空间O(1)

下标法

通过不停交换元素,使得元素和它所对应的下标相等,即nums[i] = i;当多个元素对应同一个下标,说明找到重复的元素,直接返回;判断条件nums[nums[i]] = nums[i]

代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Solution {
public int findRepeatNumber(int[] nums) {
for(int i=0;i<nums.length;i++){
while(nums[i]!=i){
// 如果条件成立,说明多个元素对应同一个下标
// 找到了重复的值,直接返回
if(nums[i]==nums[nums[i]]){
return nums[i];
}
// 如果元素与下标不相等,就进行交换
int temp = nums[i];
nums[i] = nums[temp];
nums[temp] = temp;
}
}
return -1;
}
}

二维数组中的查找

剑指 Offer 04. 二维数组中的查找 - 力扣(Leetcode)

题目要求

在一个 n * m 的二维数组中,每一行都按照从左到右 非递减 的顺序排序,每一列都按照从上到下 非递减 的顺序排序。请完成一个高效的函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。

非递减:递增的时候允许重复;

非递增:递减的时候允许重复;

例子:

现有矩阵 matrix 如下

1
2
3
4
5
6
7
[
[1, 4, 7, 11, 15],
[2, 5, 8, 12, 19],
[3, 6, 9, 16, 22],
[10, 13, 14, 17, 24],
[18, 21, 23, 26, 30]
]

给定 target = 5,返回 true

给定 target = 20,返回 false

限制:

0 <= n <= 1000
0 <= m <= 1000

思路分析

解题方法

  1. 二分查找;时间O(nlogm)
  2. 以数组左下角建立坐标系(最优);时间O(m+n)

以数组左下角建立坐标系

从二维数组左下角进行观察,可以发现,该元素比它上面的元素大,比它右边的元素小;

如果一个数比左下角元素小,那就向上移动,即col++;如果该数比左下角大,那就向右移动,即row--

注意:这里的 左下角元素 是随着移动而变化的。

代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
class Solution {
public boolean findNumberIn2DArray(int[][] matrix, int target) {
// 判断数组是否为空
if(matrix==null || matrix.length<=0 || matrix[0].length<=0){
return false;
}
// 求出数组长度和宽度
int rows = matrix.length;
int cols = matrix[0].length;
// 定义行和列
int row = rows-1;
int col = 0;
while(row>=0 && col<cols){
// 找到数组左下角的位置,并进行判断
if(target>matrix[row][col]){
// 向右移动
col++;
}else if(target<matrix[row][col]){
// 向上移动
row--;
}else{
// 相等返回true
return true;
}
}
// 没有找到
return false;
}
}

替换空格

剑指 Offer 05. 替换空格 - 力扣(Leetcode)

题目要求

请实现一个函数,把字符串 s 中的每个空格替换成"%20"。

例子:

1
2
输入:s = "We are happy."
输出:"We%20are%20happy."

限制:

0 <= s 的长度 <= 10000

思路分析

解题方法

  1. StringBuilder -> append或replace 时间O(n),空间O(n)
  2. 扩容 时间O(logn),空间O(1)

分析

解法一:创建StringBuilder,遍历字符串,使用append方法,遇到字母就直接放入,遇到空格就替换为%20,最后将StringBuilder转换为String返回即可;

解法二:创建数组,将字符串遍历放入数组,然后对数组大小进行扩容,从右向左进行遍历,遇到字母就放在右边,遇到空格就替换为%20;

代码实现

解法一:StringBuilder

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Solution {
public String replaceSpace(String s) {
// 解法一:StringBuilder
StringBuilder strB = new StringBuilder();
for(int i=0;i<s.length();i++){
if(s.charAt(i)==' '){
// 空格替换为%20
strB.append("%20");
}else{
// 字母不替换
strB.append(s.charAt(i));
}
}
// 将StringBuilder转为String并返回
return strB.toString();
}
}

解法二:扩容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
class Solution {
public String replaceSpace(String s) {
// 解法二:扩容
int count = 0; // 来记录空格数量
for(int i=0;i<s.length();i++){
if(s.charAt(i)==' '){
count++;
}
}
// 模拟扩容
char[] res = new char[s.length()+count*2];
int f = res.length-1;
// 从右向左遍历
for(int i = s.length()-1;i>=0;i--){
// 判断是否为空格
if(s.charAt(i)==' '){
// 替换空格,这里是从右向左
res[f--] = '0';
res[f--] = '2';
res[f--] = '%';
}else{
// 是字母直接替换
res[f--] = s.charAt(i);
}
}
return new String(res);
}
}

从尾到头打印链表

剑指 Offer 06. 从尾到头打印链表 - 力扣(Leetcode)

题目要求

输入一个链表的头节点,从尾到头反过来返回每个节点的值(用数组返回)。

例子:

1
2
输入:head = [1,3,2]
输出:[2,3,1]

限制:

0 <= 链表长度 <= 10000

思路分析

解题方法

  1. 栈 时间O(n),空间O(n)
  2. 反转链表
    1. 递归 时间O(n),空间O(n);
    2. 原地反转 时间O(n),空间O(1);
  3. 从右向左放入数组 时间O(n),空间O(1);

从右向左放入数组

定义一个变量存放链表节点个数,通过创建辅助链表变量,遍历获得节点个数;然后创建数组,同时遍历链表,并从右向左放入数组中;最后将数组返回。

代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) { val = x; }
* }
*/
class Solution {
public int[] reversePrint(ListNode head) {
// 判断链表是否为空
if(head==null){
return new int[0];
}
int count = 0; // 来记录节点个数
ListNode temp = head; // 创建一个辅助变量
while(temp!=null){
count++;
temp = temp.next; // 后移
}
// 创建数组
int[] arr = new int[count];
// 从右向左接收
int f = count-1; // 初始值
while(head!=null){
arr[f--] = head.val;
head = head.next;
}
return arr;
}
}

重建二叉树

剑指 Offer 07. 重建二叉树 - 力扣(Leetcode)

题目要求

输入某二叉树的前序遍历和中序遍历的结果,请构建该二叉树并返回其根节点。

假设输入的前序遍历和中序遍历的结果中都不含重复的数字。

例子:

imgage-202212101531522

1
2
Input: preorder = [3,9,20,15,7], inorder = [9,3,15,20,7]
Output: [3,9,20,null,null,15,7]

例子2:

1
2
Input: preorder = [-1], inorder = [-1]
Output: [-1]

限制:

0 <= 节点个数 <= 5000

思路分析

把根节点3看作 i

前序遍历:preStart [3,9,20,15,7] preEnd

中序遍历:inStart [9,3,15,20,7] inEnd

中序遍历左右子树:[inStart,i-1] [i+1,inEnd]

前序遍历左右子树:[preStart+1,preStart+(i-inStart)] [preStart+(i-inStart)+1,preEnd]

方括号代表左右子树,方括号里的代表左右子树各自的左右子节点;

使用HashMap存放元素和下标:Map<元素,下标>

代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
class Solution {
Map<Integer,Integer> map = new HashMap<>();
public TreeNode buildTree(int[] preorder, int[] inorder) {
// 判断树是否为空
if(preorder==null || preorder.length<=0){
return null;
}
for(int i = 0;i<inorder.length;i++){
// 将中序遍历元素和下标放入map
map.put(inorder[i],i);
}
// 调用方法得到根节点
TreeNode root = f(preorder,0,preorder.length-1,inorder,0,inorder.length-1);
return root;
}
// 定义一个方法
TreeNode f(int[] preorder,int preStart,int preEnd,int[] inorder,int inStart,int inEnd){
// 结束条件,没有左右子树
if(preStart>preEnd || inStart>inEnd){
return null;
}
// 获取根节点
TreeNode root = new TreeNode(preorder[preStart]);
// 获取根节点下标
int i = map.get(preorder[preStart]);
// 递归处理左子树的左右子节点
root.left = f(preorder,preStart+1,preStart+(i-inStart),inorder,inStart,i-1);
// 递归处理右子树的左右子节点
root.right = f(preorder,preStart+(i-inStart)+1,preEnd,inorder,i+1,inEnd);
// 返回根节点
return root;
}
}