面试题03. 找出数组中任一重复的数字
方法一:哈希表—不修改数组
由于只需要找出数组中任意一个重复的数字,因此遍历数组,遇到重复的数字即返回。为了判断一个数字是否重复遇到,使用集合存储已经遇到的数字,如果遇到的一个数字已经在集合中,则当前的数字是重复数字。
初始化集合为空集合,重复的数字 repeat = -1
遍历数组中的每个元素:
将该元素加入集合中,判断是否添加成功
如果添加失败,说明该元素已经在集合中,因此该元素是重复元素,将该元素 的值赋给 repeat,并结束遍历
返回 repeat
时间复杂度:O(n)。
遍历数组一遍。使用哈希集合(HashSet),添加元素的时间复杂度为 O(1),故总的时间复杂度是 O(n)。
空间复杂度:O(n)。不重复的每个元素都可能存入集合,因此占用 O(n) 额外空间。
class Solution {
public int findRepeatNumber(int[] nums) {
//定义格式
Set<Integer> set = new HashSet<Integer>();
int repeat = -1;
for (int num : nums) {
if (!set.add(num)) {
repeat = num;
//跳出整个循环,continue(kentiniu)直接进入下一次循环,不用管if语句
break;
}
}
return repeat;
}
}
方法二:把数组视为哈希表—修改数组
如果没有重复数字,那么正常排序后,数字i应该在下标为i的位置,所以思路是重头扫描数组,遇到下标为i的数字如果不是i的话,(假设为m),那么我们就拿与下标m的数字交换。在交换过程中,如果有重复的数字发生,那么终止返回ture
时间复杂度o(n) 空间复杂度o(1)
class Solution {
public int findRepeatNumber(int[] nums) {
int temp;
for(int i=0;i<nums.length;i++){
//**索引与值**相同时候略过,只考虑不相同的时候
while (nums[i]!=i){
//如果**值与以值为索引的值**相同就重复
if(nums[i]==nums[nums[i]]){
return nums[i];
}
//否则进行值与以值为索引的值交换
temp=nums[i];
//很奇怪 如果写nums[i]=nums[nums[i]]就会超时
nums[i]=nums[temp];
nums[temp]=temp;
}
}
return -1;
}
}
方法三 二分法–不修改数组 o(logn) o(1)
面试题04. 二维数组中的查找
时间复杂度 O(rows + cols) 也就是行数+列数 左上做法
class Solution {
public boolean findNumberIn2DArray(int[][] matrix, int target) {
//matrix.length==0 需要判断不然会报错 非法索引访问
if(matrix==null||matrix.length==0){
return false;
}
int rows=matrix.length-1;
int cols=(matrix[0].length)-1;
int i=rows;int j=0;
while(i>=0&&j<=cols){
//三种情况 大、小、相等
if(target>matrix[i][j]){
j++;
}else if(target<matrix[i][j]){
i--;
}else{
return true;
}
}
return false;
}
}
面试题05. 替换空格
不利用额外空间,在原地修改 int len1 = str.length()会报错
public class Solution {
public String replaceSpace(StringBuffer str) {
//初始长度
int len1 = str.length() - 1;
//字符串扩容
for(int i = 0; i <= len1; i++){
if(str.charAt(i) == ' '){
//加了两个空格
str.append(" ");
}
}
//扩充长度
int len2 = str.length() - 1
//尾部替换扩充后的str
while(len2 > len1 && len1 >= 0){
//获取值
char c = str.charAt(len1--);
if(c == ' '){
str.setCharAt(len2--, '0');
str.setCharAt(len2--, '2');
str.setCharAt(len2--, '%');
}else{
str.setCharAt(len2--, c);
}
}
return str.toString();
}
}
class Solution {
public String replaceSpace(String s) {
//需要额外空间
StringBuilder sb = new StringBuilder();
for(int i = 0 ; i < s.length(); i++){
//获取值
char c = s.charAt(i);
if(c == ' ') sb.append("%20");
else sb.append(c);
}
return sb.toString();
}
}
//replaceAll(" ", “%20”);
class Solution {
public String replaceSpace(String s) {
return s.replaceAll(" ", "%20");
}
}
class Solution {
public String replaceSpace(String s) {
//s.toCharArray()转换成字符串数组
char[] chars = s.toCharArray();
char[] newChars = new char[3 * chars.length];
int size = 0;
for (char c : chars) {
if (c == ' '){
newChars[size++] = '%';
newChars[size++] = '2';
newChars[size++] = '0';
} else {
newChars[size++] = c;
}
}
String newStr = new String(newChars, 0, size);
return newStr;
}
}
面试题06. 从尾到头打印链表
一个栈搞定
时间复杂度:O(n)。正向遍历一遍链表,然后从栈弹出全部节点,等于又反向遍历一遍链表。
空间复杂度:O(n)。额外使用一个栈存储链表中的每个节点。
class Solution {
public int[] reversePrint(ListNode head) {
Stack<ListNode> stack = new Stack<ListNode>();
ListNode temp = head;
//进栈
while (temp != null) {
stack.push(temp);
temp = temp.next;
}
//stack.size()
int size = stack.size();
int[] print = new int[size];
for (int i = 0; i < size; i++) {
print[i] = stack.pop().val;
}
return print;
}
}
class Solution {
// 执行用时 : 0 ms, 在所有 Java 提交中击败了 100.00% 的用户
// 内存消耗 : 39.8 MB, 在所有 Java 提交中击败了 100.00% 的用户
// 我就不使用栈,就不使用递归,反正怎么样也是扫描两趟,为什么要额外分配空间呢?
// 感谢 @谢飞机 的灵感。
public static int[] reversePrint(ListNode head) {
ListNode node = head;
int count = 0;
//计算链表节点数
while (node != null) {
++count;
node = node.next;
}
int[] nums = new int[count];
node = head;
//数组从后赋值
for (int i = count - 1; i >= 0; --i) {
nums[i] = node.val;
node = node.next;
}
return nums;
}
}
面试题09. 用两个栈实现队列
使用Stack(s大k)的方式来做这道题,会造成速度较慢; 原因的话是Stack继承了Vector(外kt)接口,而Vector底层是一个Object[]数组,那么就要考虑空间扩容和移位的问题了。 可以使用LinkedList来做Stack的容器,因为LinkedList实现了Deque(带k)接口,所以Stack能做的事LinkedList都能做,其本身结构是个双向链表,扩容消耗少。
class CQueue {
LinkedList<Integer> stack1;
LinkedList<Integer> stack2;
public CQueue() {
stack1 = new LinkedList<>();
stack2 = new LinkedList<>();
}
//添加
public void appendTail(int value) {
//外来输入 value
stack1.add(value);
}
//删除
public int deleteHead() {
//考虑三种情况 stack2为空(stack1为空,stack1不为空)satck2不为空
if (stack2.isEmpty()) {
if (stack1.isEmpty()) return -1;
//stack1不为空,将内部值都添加到stack2中
while (!stack1.isEmpty()) {
stack2.add(stack1.pop());
}
return stack2.pop();
} else
return stack2.pop();
}
}
面试题10- I. 斐波那契数列
(a+b)%c = (a%c+b%c)%c
最后取模应该会超过int型最大值
01235
class Solution {
public int fib(int n) {
int res =0;
if(n==0||n==1){
return n;
}
int a=1;
int b=0;
for(int i=1;i<n;i++){
a=a+b;
//b=上一个a
b=a-b;
//最后取模应该会超过int型最大值
a%=1000000007;
}
return a;
}
}
面试题10- II. 青蛙跳台阶问题
11235
class Solution {
public int numWays(int n) {
if(n==0||n==1){
return 1;
}
int a=1;
int b=1;
for(int i=1;i<n;i++){
a=a+b;
b=a-b;
a%=1000000007;
}
return a;
}
}
面试题10- II. 青蛙变态跳台阶问题
public class Solution {
public int JumpFloorII(int target) {
return 1<<(target-1);
//return (int)Math.pow(2,target-1);
}
}
面试题11. 旋转数组的最小数字
首尾双指针,中间总是和右边比,去重 时间复杂度 O(log2N)O: 在特例情况下(例如 [1,1,1,1]),会退化到 O(N)。
空间复杂度 O(1): i , j , m指针使用常数大小的额外空间。
class Solution {
public int minArray(int[] numbers) {
int i = 0, j = numbers.length - 1;
while (i < j) {
int m = (i + j) / 2;
//中间总是和右边比
if (numbers[m] > numbers[j]) i = m + 1;
else if (numbers[m] < numbers[j]) j = m;
//去重
else j--;
}
return numbers[i];
}
}
面试题15. 二进制中1的个数
时间复杂度 O(M): n&(n−1) 操作仅有减法和与运算,占用 O(1);设 M为二进制数字 n中 1 的个数,则需循环 M次(每轮消去一个 1),占用 O(M)。
空间复杂度 O(1) : 变量 res 使用常数大小额外空间。
把一个整数减去1,再和原整数做与运算,会把该整数最右边一个1变成0
public class Solution {
// you need to treat n as an unsigned value
public int hammingWeight(int n) {
int count=0;
while(n!=0){
n=n&(n-1);
count++;
}
return count;
}
}
面试题17. 打印从1到最大的n位数(待研究)
主要考大数问题,用字符串模拟加法
public class solution {
public void printNumbers(int n) {
StringBuilder str = new StringBuilder();
// 将str初始化为n个'0'字符组成的字符串
for (int i = 0; i < n; i++) {
str.append('0');
}
while(!increment(str)){
// 去掉左侧的0
int index = 0;
while (index < str.length() && str.charAt(index) == '0'){
index++;
}
System.out.println(str.toString().substring(index));
}
}
public boolean increment(StringBuilder str) {
boolean isOverflow = false;
for (int i = str.length() - 1; i >= 0; i--) {
char s = (char)(str.charAt(i) + 1);
// 如果s大于'9'则发生进位
if (s > '9') {
str.replace(i, i + 1, "0");
if (i == 0) {
isOverflow = true;
}
}
// 没发生进位则跳出for循环
else {
str.replace(i, i + 1, String.valueOf(s));
break;
}
}
return isOverflow;
}
}
class Solution {
public int[] printNumbers(int n) {
double max = Math.pow(10, n);
//强转 最大数-1
int len = (int) max-1;
int [] res = new int [len];
for(int i=0;i<len;i++){
res[i]=i+1;
}
return res;
}
}
面试题18. 删除链表的节点
时间复杂度O(1)
class deleteNode {public static ListNode deleteNode(ListNode head, ListNode val){
if (head == null || val == null){
return null;
}
if (val.next != null){ // 待删除节点不是尾节点O(1)
ListNode next = val.next;
val.val = next.val;
val.next = next.next;
//需要删除的节点后面没有节点时,即要删除的节点位于尾节点
//这时有两个情况:1.链表中只有这一个节点,此时删除后链表为null。2.需要删除的节点前面还有节点。
} else if (head == val){ // 待删除节点只有一个节点,此节点为头节点
head = null;
} else { // 待删除节点为尾节点O(n)
ListNode cur = head;
while (cur.next != val){
cur = cur.next;
}
cur.next = null;
}
return head;
}
}
时间复杂度O(n) 空间复杂度O(1)
public ListNode deleteNode1(ListNode head, int val) {
if(head==null) return head;
if(head.val==val) return head.next;
ListNode cur = head;
//先找到要删除的结点
while(cur.next!=null&&cur.next.val!=val){
cur = cur.next;
}
if(cur.next!=null)
//直接指向下一个
cur.next=cur.next.next;
//而如果是cur.next==null导致的跳出循环,则说明链表中查询完毕也没有找到对应节点,不对链表进行修改。
return head;
}
面试题21. 调整数组顺序使奇数位于偶数前面
时间复杂度 O(N) : N为数组 nums 长度,双指针 i, j共同遍历整个数组。
空间复杂度 O(1): 双指针 i, j使用常数大小的额外空间。
class Solution {
public int[] exchange(int[] nums) {
int i = 0, j = nums.length - 1, tmp;
while(i < j) {
while(i < j && (nums[i] & 1) == 1) i++;
while(i < j && (nums[j] & 1) == 0) j--;
tmp = nums[i];
nums[i] = nums[j];
nums[j] = tmp;
}
return nums;
}
}
面试题22. 链表中倒数第k个节点
快慢指针,先让快指针走k步,然后两个指针同步走,当快指针走到头时,慢指针就是链表倒数第k个节点。 1~k-1
class Solution {
public ListNode getKthFromEnd(ListNode head, int k) {
ListNode fast=head;
ListNode slow=head;
for(int i=1;i<=k-1;i++){
fast=fast.next;
}
while(fast.next!=null){
slow=slow.next;
fast=fast.next;
}
return slow;
}
}
面试题24. 反转链表
双指针 一前一后初始为空
class Solution {
public ListNode reverseList(ListNode head) {
ListNode pre=null;
ListNode temp=null;
if(head==null){
return null;
}
if(head.next==null){
return head;
}
while(head!=null){
temp=head.next;
head.next=pre;
pre=head;
head=temp;
}
return pre;
}
}
面试题25. 合并两个排序的链表
新创结点 别忘了考虑两个链表不一样长的情况
class Solution {
public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
ListNode p =new ListNode(-1);
ListNode q =p;
while(l1!=null&&l2!=null){
if(l1.val<=l2.val){
p.next=l1;
l1=l1.next;
}else{
p.next=l2;
l2=l2.next;
}
p=p.next;
}
p.next=l1!=null?l1:l2;
return q.next;
}
}
面试题26. 树的子结构
程序也不知道是从哪个节点开始一模一样的,那只好从根出发慢慢比咯。递归实现
class Solution {
public boolean isSubStructure(TreeNode A, TreeNode B) {
if(B==null||A==null) return false;
//如果结点不等,则接着和A树的左右结点比
if(!isPartSame(A,B))
return isSubStructure(A.left,B) || isSubStructure(A.right,B);
return true;
}
public boolean isPartSame(TreeNode A, TreeNode B) {
if(B==null) return true;
if(A==null) return false;
if(A.val==B.val)
return isPartSame(A.left,B.left) && isPartSame(A.right,B.right);
else
return false;
}
}
面试题27. 二叉树的镜像
请完成一个函数,输入一个二叉树,该函数输出它的镜像。
class Solution {
public TreeNode mirrorTree(TreeNode root) {
if(root == null) return null;
//在递归右子节点 “root.left=mirrorTree(root.right);” 执行完毕后, root.leftroot.leftroot.left 的值已经发生改变,此时递归左子节点 mirrorTree(root.left) 则会出问题
TreeNode tmp = root.left;
root.left = mirrorTree(root.right);
root.right = mirrorTree(tmp);
return root;
}
}
一个栈解决
class Solution {
public TreeNode mirrorTree(TreeNode root) {
if(root == null) return null;
Stack<TreeNode> stack = new Stack<>() {{ add(root); }};
while(!stack.isEmpty()) {
TreeNode node = stack.pop();
//把左右子树放进去
if(node.left != null) stack.add(node.left);
if(node.right != null) stack.add(node.right);
//左右结点交换
TreeNode tmp = node.left;
node.left = node.right;
node.right = tmp;
}
return root;
}
}
面试题28. 对称的二叉树
class Solution {
public boolean isSymmetric(TreeNode root) {
return root == null ? true : recur(root.left, root.right);
}
boolean recur(TreeNode L, TreeNode R) {
if(L == null && R == null) return true;
if(L == null || R == null || L.val != R.val) return false;
return recur(L.left, R.right) && recur(L.right, R.left);
}
}
面试题29. 顺时针打印矩阵
class Solution {
public int[] spiralOrder(int[][] matrix) {
if(matrix.length == 0) return new int[0];
int l = 0, r = matrix[0].length - 1, t = 0, b = matrix.length - 1, x = 0;
//数组的长度=长*宽
int[] res = new int[(r + 1) * (b + 1)];
//默认循环条件
while(true) {
for(int i = l; i <= r; i++) res[x++] = matrix[t][i]; // left to right.
if(++t > b) break;
for(int i = t; i <= b; i++) res[x++] = matrix[i][r]; // top to bottom.
if(l > --r) break;
for(int i = r; i >= l; i--) res[x++] = matrix[b][i]; // right to left.
if(t > --b) break;
for(int i = b; i >= t; i--) res[x++] = matrix[i][l]; // bottom to top.
if(++l > r) break;
}
return res;
}
}
面试题30. 包含min函数的栈
class MinStack {
//A原生插入,B栈顶存最小元素
Stack<Integer> a, b;
public MinStack() {
a = new Stack<>();
b = new Stack<>();
}
public void push(int x) {
a.add(x);
if(b.empty() || b.peek() >= x)
b.add(x);
}
public void pop() {
if(a.peek().equals(b.peek())){ // 未拆箱,因此要用 equals()
a.pop();
b.pop();
} else { // 这里要 else ,否则会多出栈一次
a.pop();
}
}
public int top() {
return a.peek();
}
public int min() {
return b.peek();
}
}
面试题32 - II. 从上到下打印二叉树 II
题目要求的二叉树的 从上至下 打印(即按层打印),又称为二叉树的 广度优先搜索(BFS)。BFS 通常借助 队列 的先入先出特性来实现。
时间复杂度 O(N): N为二叉树的节点数量,即 BFS 需循环 N次。
空间复杂度 O(N): 最差情况下,即当树为平衡二叉树时,最多有 N/2 个树节点同时在 queue 中,使用 O(N)大小的额外空间。
class Solution {
public List<List<Integer>> levelOrder(TreeNode root) {
Queue<TreeNode> queue = new LinkedList<>();
//一行一个元素
List<List<Integer>> res = new ArrayList<>();
if(root != null) queue.add(root);
while(!queue.isEmpty()) {
//临时列表
List<Integer> tmp = new ArrayList<>();
//循环次数为当前层节点数(即队列 queue 长度)
for(int i = queue.size(); i > 0; i--) {
TreeNode node = queue.poll();
tmp.add(node.val);
if(node.left != null) queue.add(node.left);
if(node.right != null) queue.add(node.right);
}
res.add(tmp);
}
return res;
}
}