二刷剑指offer 1-21

面试题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;
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值