剑指Offer
Day 1
3. 数组中重复的数字
解题关键: 数组nums的长度为n,且里面的数字取值范围是[0, n-1]。
思路一
通过开辟一个新的数组array,array的index代表nums数组中的值,array的值代表nums数组中元素出现的次数。
复杂度: 时间复杂度:O(n)
,空间复杂度:O(n)
public int findRepeatNumber(int[] nums) {
int[] array = new int[nums.length];
int ans = -1;
for(int i = 0; i < nums.length; i++) {
array[nums[i]] += 1;
if (array[nums[i]] > 1) {
ans = nums[i];
}
}
return ans;
}
思路二
通过交换,让每个数字出现在其应该出现的位置
复杂度: 时间复杂度:O(n)
,空间复杂度:O(1)
public int findRepeatNumber(int[] nums) {
boolean flag = false;
int ans = -1;
for (int i = 0; i < nums.length; i++) {
if (nums[i] == i) {
continue;
}
while (nums[i] != i) {
if (nums[i] == nums[nums[i]]) {
ans = nums[i];
flag = true;
break;
} else {
int tmp = nums[i];
nums[i] = nums[nums[i]];
nums[tmp] = tmp;
}
}
if (flag == true) {
break;
}
}
return ans;
}
4. 二维数组中的查找
二维数组的规律:每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。
思路一
利用规律进行查找元素:从右上角开始,假设要查找的数字是x。
- 如果当前数字大于x,那么就向左查找;
- 如果当前数字小于x,那么就向下查找;
- 如果当前数字等于x,那么就查找完毕,返回true;
- 如果越界了,那么查找完毕,返回false。
复杂度: 时间复杂度:O(n)
,空间复杂度:O(1)
public boolean findNumberIn2DArray(int[][] matrix, int target) {
if (matrix == null || matrix.length == 0 || matrix[0].length == 0) {
return false;
}
int width = matrix[0].length;
int height = matrix.length;
int row = 0;
int col = width - 1;
boolean ans = false;
while (row < height && col >= 0) {
if (matrix[row][col] == target) {
ans = true;
break;
} else if (matrix[row][col] > target) {
col--;
} else if (matrix[row][col] < target) {
row++;
}
}
return ans;
}
5. 替换空格
请实现一个函数,把字符串 s
中的每个空格替换成"%20"。
思路一
使用 StringBuilder
,加额外的空间进行组装
复杂度: 时间复杂度:O(n)
,空间复杂度:O(n)
public String replaceSpace(String s) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < s.length(); i++) {
if (s.charAt(i) == ' ') {
sb.append("%20");
} else {
sb.append(s.charAt(i));
}
}
return sb.toString();
}
思路二
使用char[]数组,该数组的长度设置为当前长度的3倍,确保即使当前字符串全部是空格,也可以放得下。
复杂度: 时间复杂度:O(n)
,空间复杂度:O(3n)
public String replaceSpace(String s) {
char[] chars = new char[s.length() * 3];
char[] ss = s.toCharArray();
int index = 0;
for (char c : ss) {
if (c == ' ') {
chars[index++] = '%';
chars[index++] = '2';
chars[index++] = '0';
} else {
chars[index++] = c;
}
}
return new String(chars, 0, index);
}
Tips: 将字符数组转化成String的方式
String(byte[] bytes, int offset, int length);
Constructs a new String
by decoding the specified subarray of bytes using the platform’s default charset.
6. 从尾到头打印链表
思路一:
不考虑时间复杂度或者其他因素,可以直接通过将链表压入栈,紧接着弹出,就可以打印链表。
Java中官方JDK不推荐使用Stack。但是就算法题来说,为了说明算法还是使用了Stack
public int[] reversePrint(ListNode head) {
Stack<Integer> stack = new Stack();
while (head != null) {
stack.push(head.val);
head = head.next;
}
int[] ans = new int[stack.size()];
for (int i = 0; i < ans.length; i++) {
ans[i] = stack.pop();
}
return ans;
}
思路二:
本题还可以采用递归实现
递归终止条件是:head.next == null;
递归的终止操作是将其加入到 ArrayList 中
ArrayList<Integer> list = new ArrayList<>();
public int[] reversePrint(ListNode head) {
recur(head);
int[] ans = new int[list.size()];
for(int i = 0; i < ans.length; i++) {
ans[i] = list.get(i);
}
return ans;
}
public void recur(ListNode head) {
if (head == null) return;
recur(head.next);
list.add(head.val);
}
7. 重建二叉树
根据二叉树遍历的顺序,
前序遍历:按照 根节点-左子树-右子树 遍历
中序遍历:按照 左子树-根节点-右子树 遍历
思路:
递归终止条件: 当 in_st > in_end
时,说明这个时候已经遍历完了,直接返回null。
构建 node
节点,确定当前递归的 node
的 index
即为 i
。
TreeNode node = new TreeNode(preorder[pre_st]);
int i = map.get(preorder[pre_st]);
递归遍历 node
的左子节点,递归遍历 node
的右子节点。
最后返回 node
。
复杂度: 时间复杂度:O(n)
,空间复杂度:O(n)
HashMap<Integer, Integer> map = new HashMap<>();
public TreeNode buildTree(int[] preorder, int[] inorder) {
for (int i = 0; i < inorder.length; i++) {
map.put(inorder[i], i);
}
return recur(preorder, 0, 0, inorder.length - 1);
}
public TreeNode recur(int[] preorder, int pre_st, int in_st, int in_end) {
if (in_st > in_end) {
return null;
}
TreeNode node = new TreeNode(preorder[pre_st]);
int i = map.get(preorder[pre_st]);
node.left = recur(preorder, pre_st + 1, in_st, i - 1);
node.right = recur(preorder, pre_st + i - in_st + 1, i + 1, in_end);
return node;
}