文章目录
- 剑指 Offer 03. 数组中重复的数字
- 剑指 Offer 05. 替换空格
- 剑指 Offer 06. 从尾到头打印链表
- 剑指 Offer 07. 重建二叉树
- 剑指 Offer 09. 用两个栈实现队列
- 剑指 Offer 10- I. 斐波那契数列
- 剑指 Offer 10- II. 青蛙跳台阶问题
- 剑指 Offer 11. 旋转数组的最小数字
- 剑指 Offer 15. 二进制中1的个数
- 剑指 Offer 18. 删除链表的节点
- 剑指 Offer 21. 调整数组顺序使奇数位于偶数前面
- 剑指 Offer 22. 链表中倒数第k个节点
- 剑指 Offer 24. 反转链表
- 剑指 Offer 25. 合并两个排序的链表
- 剑指 Offer 26. 树的子结构
- 剑指 Offer 27. 二叉树的镜像
- 剑指 Offer 28. 对称的二叉树
- 剑指 Offer 30. 包含min函数的栈
- 剑指 Offer 31. 栈的压入、弹出序列
- 剑指 Offer 32 - I. 从上到下打印二叉树
- 剑指 Offer 32 - II. 从上到下打印二叉树 II
- 剑指 Offer 32 - III. 从上到下打印二叉树 III
剑指 Offer 03. 数组中重复的数字
找出数组中重复的数字。
在一个长度为 n 的数组 nums 里的所有数字都在 0~n-1 的范围内。数组中某些数字是重复的,但不知道有几个数字重复了,也不知道每个数字重复了几次。请找出数组中任意一个重复的数字。
方法1:哈希表
class Solution {
public int findRepeatNumber(int[] nums) {
HashSet<Integer> set = new HashSet<>();
for(int i=0;i<nums.length;i++){
if(set.contains(nums[i])){
return nums[i];
}
set.add(nums[i]);
}
return -1;
}
}
方法2:ArrayList数组,超出时间限制
class Solution {
public int findRepeatNumber(int[] nums) {
ArrayList<Integer> list = new ArrayList<>();
for(int i=0;i<nums.length;i++){
if(list.contains(nums[i])){
return nums[i];
}
list.add(nums[i]);
}
return -1;
}
}
方法3:暴力法,超出时间限制
class Solution {
public int findRepeatNumber(int[] nums) {
for(int i=0;i<nums.length;i++){
for(int j=i+1;j<nums.length;j++){
if(nums[i]==nums[j]){
return nums[i];
}
}
}
return -1;
}
}
方法4:根据题目条件,所有的数字范围都在0~n-1之间,可以让num[i]和索引i一一对应起来。
class Solution {
public int findRepeatNumber(int[] nums) {
int i=0;
while (i<nums.length){
if(nums[i]==i){
i++;
continue;
}
if(nums[i] == nums[nums[i]]) return nums[i];
int temp = nums[i];
nums[i] = nums[temp];
nums[temp] = temp;
}
return -1;
}
}
剑指 Offer 05. 替换空格
请实现一个函数,把字符串 s
中的每个空格替换成"%20"。
方法:遍历字符串,遇到空格就拼接%20
class Solution {
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();
}
}
剑指 Offer 06. 从尾到头打印链表
输入一个链表的头节点,从尾到头反过来返回每个节点的值(用数组返回)。
方法1:递归
class Solution {
ArrayList<Integer> list = new ArrayList<>();
public int[] reversePrint(ListNode head) {
dfs(head);
int[] arr = new int[list.size()];
for(int i=0;i<list.size();i++){
arr[i] = list.get(i);
}
return arr;
}
private void dfs(ListNode root) {
if(root==null) return;
dfs(root.next);
list.add(root.val);
}
}
方法2:遍历链表并将值存放在ArrayList中
class Solution {
ArrayList<Integer> list = new ArrayList<>();
public int[] reversePrint(ListNode head) {
ListNode cur = head;
while (cur!=null){
list.add(0,cur.val);
cur = cur.next;
}
int[] arr = new int[list.size()];
for(int i=0;i<arr.length;i++){
arr[i] = list.get(i);
}
return arr;
}
}
方法3:使用栈先进后出的特点
class Solution {
ArrayList<Integer> list = new ArrayList<>();
public int[] reversePrint(ListNode head) {
Stack<Integer> stack = new Stack<>();
ListNode cur = head;
while (cur!=null){
stack.push(cur.val);
cur = cur.next;
}
int[] arr = new int[stack.size()];
for(int i=0;i<arr.length;i++){
arr[i] = stack.pop();
}
return arr;
}
}
剑指 Offer 07. 重建二叉树
输入某二叉树的前序遍历和中序遍历的结果,请重建该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。
class Solution {
HashMap<Integer,Integer> map = new HashMap<>();
public TreeNode buildTree(int[] preorder, int[] inorder) {
int preleft = 0;
int prerigt = preorder.length-1;
int inleft = 0;
int inright = inorder.length-1;
for(int i=0;i<inorder.length;i++){
map.put(inorder[i],i);
}
return rebuild(preleft,prerigt,inleft,inright,preorder);
}
private TreeNode rebuild(int preleft, int prerigt, int inleft, int inright, int[] preorder ) {
if(preleft>prerigt || inleft>inright ) return null;
int rootVal = preorder[preleft];
TreeNode root = new TreeNode(rootVal);
int index = map.get(rootVal);
root.left = rebuild(preleft+1,preleft+index-inleft,inleft,index-1,preorder);
root.right = rebuild(preleft+index-inleft+1,prerigt,index+1,inright,preorder);
return root;
}
}
剑指 Offer 09. 用两个栈实现队列
用两个栈实现一个队列。队列的声明如下,请实现它的两个函数 appendTail 和 deleteHead ,分别完成在队列尾部插入整数和在队列头部删除整数的功能。(若队列中没有元素,deleteHead 操作返回 -1 )
方法:两个栈实现队列,只要stackB中有数据就弹出,只有stackB中没有数据才能将A栈数据压进去
class CQueue {
Stack<Integer> stackA;
Stack<Integer> stackB;
public CQueue() {
stackA = new Stack<>();
stackB = new Stack<>();
}
public void appendTail(int value) {
stackA.push(value);
}
public int deleteHead() {
if(!stackB.isEmpty()) return stackB.pop();
if(stackA.isEmpty()) return -1;
while (!stackA.isEmpty()){
stackB.push(stackA.pop());
}
return stackB.pop();
}
}
剑指 Offer 10- I. 斐波那契数列
写一个函数,输入 n ,求斐波那契(Fibonacci)数列的第 n 项。斐波那契数列的定义如下:
F(0) = 0, F(1) = 1
F(N) = F(N - 1) + F(N - 2), 其中 N > 1.
斐波那契数列由 0 和 1 开始,之后的斐波那契数就是由之前的两数相加而得出。
答案需要取模 1e9+7(1000000007),如计算初始结果为:1000000008,请返回 1。
方法1:动态规划
class Solution {
public int fib(int n) {
if(n==0) return 0;
if(n==1) return 1;
if(n==2) return 1;
int[] dp = new int[n+1];
dp[0] = 0;
dp[1] = 1;
dp[2] = 1;
for(int i=3;i<=n;i++){
dp[i] = dp[i-1]+dp[i-2];
dp[i] = dp[i] % 1000000007;
}
return dp[n];
}
}
方法2:递归,超出时间限制
class Solution {
public int fib(int n) {
if(n==0) return 0;
if(n==1) return 1;
return (fib(n-1) +fib(n-2))% 1000000007;
}
}
剑指 Offer 10- II. 青蛙跳台阶问题
一只青蛙一次可以跳上1级台阶,也可以跳上2级台阶。求该青蛙跳上一个 n 级的台阶总共有多少种跳法。
答案需要取模 1e9+7(1000000007),如计算初始结果为:1000000008,请返回 1。
class Solution {
public int numWays(int n) {
//如果没有这些条件,会导致给dp赋值的时候数组越界
if(n==0) return 1;
if(n==1) return 1;
if(n==2) return 2;
int[] dp = new int[n+1];
dp[1] = 1;
dp[2] = 2;
for(int i=3;i<=n;i++){
dp[i] = dp[i-1]+dp[i-2];
dp[i] = dp[i] % 1000000007;
}
return dp[n];
}
}
剑指 Offer 11. 旋转数组的最小数字
把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。输入一个递增排序的数组的一个旋转,输出旋转数组的最小元素。例如,数组 [3,4,5,1,2] 为 [1,2,3,4,5] 的一个旋转,该数组的最小值为1。
方法:二分查找
class Solution {
public int minArray(int[] numbers) {
if(numbers.length==0 || numbers==null) return 0;
int start = 0;
int end = numbers.length-1;
while (start+1<end){
int mid = start + (end-start)/2;
if(numbers[mid]<numbers[end]){
end = mid;
}else if(numbers[mid]>numbers[end]){
start = mid;
}else if(numbers[mid]==numbers[end]){
end--;
}
}
if(numbers[start]<numbers[end]) return numbers[start];
else return numbers[end];
}
}
剑指 Offer 15. 二进制中1的个数
请实现一个函数,输入一个整数(以二进制串形式),输出该数二进制表示中 1 的个数。例如,把 9 表示成二进制是 1001,有 2 位是 1。因此,如果输入 9,则该函数输出 2
public class Solution {
// you need to treat n as an unsigned value
public int hammingWeight(int n) {
int res = 0;
while (n!=0){
res += n & 1;
n = n>>>1;
}
return res;
}
}
剑指 Offer 18. 删除链表的节点
给定单向链表的头指针和一个要删除的节点的值,定义一个函数删除该节点。返回删除后的链表的头节点。
方法1:
class Solution {
public ListNode deleteNode(ListNode head, int val) {
ListNode dummy = new ListNode(0);
dummy.next = head;
ListNode cur = dummy;
while (cur!=null && cur.next!=null){
if(cur.next.val==val){
cur.next = cur.next.next;
}
//为了删除重复节点,所以不要加else
cur = cur.next;
}
return dummy.next;
}
}
方法2:
class Solution {
public ListNode deleteNode(ListNode head, int val) {
ListNode dummy = new ListNode(0);
dummy.next = head;
ListNode cur = dummy;
while (true){
if(cur.next.val==val){
cur.next = cur.next.next;
//为了删除重复节点,所以加break
break;
}
cur = cur.next;
}
return dummy.next;
}
}
剑指 Offer 21. 调整数组顺序使奇数位于偶数前面
输入一个整数数组,实现一个函数来调整该数组中数字的顺序,使得所有奇数位于数组的前半部分,所有偶数位于数组的后半部分。
方法1:插入排序算法
class Solution {
public int[] exchange(int[] nums) {
if(nums.length==0 || nums==null) return new int[0];
for(int i=1;i<nums.length;i++){
int insertNum = nums[i];
if(nums[i]%2==1){
int insertIndex = i;
while (insertIndex>0 && nums[insertIndex-1]%2==0){
nums[insertIndex] = nums[insertIndex-1];
insertIndex--;
}
nums[insertIndex] = insertNum;
}
}
return nums;
}
}
方法2:双指针
class Solution {
public int[] exchange(int[] nums) {
if(nums.length==0) return new int[0];
int left = 0;
int right = nums.length-1;
while (left<right){
while (left<right && nums[left]%2==1) left++;
while (left<right && nums[right]%2==0) right--;
int tmp = nums[left];
nums[left] = nums[right];
nums[right] = tmp;
}
return nums;
}
}
剑指 Offer 22. 链表中倒数第k个节点
输入一个链表,输出该链表中倒数第k个节点。为了符合大多数人的习惯,本题从1开始计数,即链表的尾节点是倒数第1个节点。例如,一个链表有6个节点,从头节点开始,它们的值依次是1、2、3、4、5、6。这个链表的倒数第3个节点是值为4的节点。
方法:快慢指针,先让快指针走k步,然后让快慢指针同时走
class Solution {
public ListNode getKthFromEnd(ListNode head, int k) {
ListNode fast = head;
ListNode slow = head;
for(int i=0;i<k;i++){
fast = fast.next;
}
while (fast!=null){
slow = slow.next;
fast = fast.next;
}
return slow;
}
}
剑指 Offer 24. 反转链表
定义一个函数,输入一个链表的头节点,反转该链表并输出反转后链表的头节点。
class Solution {
public ListNode reverseList(ListNode head) {
if(head==null) return null;
ListNode pre = null;
ListNode cur = head;
//注意是cur!=null,每个数都要cur.next=pre
while (cur!=null){
ListNode temp = cur.next;
cur.next = pre;
pre = cur;
cur = temp;
}
return pre;
}
}
剑指 Offer 25. 合并两个排序的链表
输入两个递增排序的链表,合并这两个链表并使新链表中的节点仍然是递增排序的。
class Solution {
public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
ListNode dummy = new ListNode(0);
ListNode cur = dummy;
while (l1!=null && l2!=null){
if(l1.val<l2.val){
cur.next = l1;
l1 = l1.next;
}else{
cur.next = l2;
l2 = l2.next;
}
cur = cur.next;
}
if(l1!=null){
cur.next = l1;
}else if(l2!=null){
cur.next = l2;
}
return dummy.next;
}
}
剑指 Offer 26. 树的子结构
输入两棵二叉树A和B,判断B是不是A的子结构。(约定空树不是任意一个树的子结构)。B是A的子结构, 即 A中有出现和B相同的结构和节点值。
class Solution {
public boolean isSubStructure(TreeNode A, TreeNode B) {
if(B == null) return false;
if(A == null) return false;
return dfs(A,B) || isSubStructure(A.left,B) || isSubStructure(A.right,B);
}
private boolean dfs(TreeNode A, TreeNode B) {
// 如果B==null说明B这颗树的节点比较完了,返回true
if(B==null) return true;
// 如果B!=null,但是A==null,说明两颗子树不相等,返回false
if(A==null) return false;
return A.val==B.val && dfs(A.left,B.left) && dfs(A.right,B.right);
}
}
剑指 Offer 27. 二叉树的镜像
请完成一个函数,输入一个二叉树,该函数输出它的镜像。
class Solution {
public TreeNode mirrorTree(TreeNode root) {
if(root==null) return null;
return dfs(root);
}
private TreeNode dfs(TreeNode root) {
if(root==null) return null;
//递归的交换左右子树的节点
TreeNode temp = root.left;
root.left = root.right;
root.right = temp;
dfs(root.left);
dfs(root.right);
return root;
}
}
剑指 Offer 28. 对称的二叉树
请实现一个函数,用来判断一棵二叉树是不是对称的。如果一棵二叉树和它的镜像一样,那么它是对称的。
class Solution {
public boolean isSymmetric(TreeNode root) {
if(root==null) return true;
return dfs(root.left,root.right);
}
private boolean dfs(TreeNode A, TreeNode B) {
if(A==null && B==null) return true;
// if(A==null && B!=null) return false;
// if(A!=null && B==null) return false;
if(A==null || B==null) return false;
return A.val==B.val && dfs(A.left,B.right) && dfs(A.right,B.left);
}
}
剑指 Offer 30. 包含min函数的栈
定义栈的数据结构,请在该类型中实现一个能够得到栈的最小元素的 min 函数在该栈中,调用 min、push 及 pop 的时间复杂度都是 O(1)。
class MinStack {
Stack<Integer> stackA ;
Stack<Integer> stackB;
/** initialize your data structure here. */
public MinStack() {
stackA = new Stack<>();
stackB = new Stack<>();
}
public void push(int x) {
stackA.push(x);
if(stackB.isEmpty() || stackB.peek()>=x){
stackB.push(x);
}
}
public void pop() {
//注意比较弹出的值相等时使用equals
if(stackA.pop().equals(stackB.peek())){
stackB.pop();
}
}
public int top() {
return stackA.peek();
}
public int min() {
return stackB.peek();
}
}
剑指 Offer 31. 栈的压入、弹出序列
输入两个整数序列,第一个序列表示栈的压入顺序,请判断第二个序列是否为该栈的弹出顺序。假设压入栈的所有数字均不相等。例如,序列 {1,2,3,4,5} 是某栈的压栈序列,序列 {4,5,3,2,1} 是该压栈序列对应的一个弹出序列,但 {4,3,5,1,2} 就不可能是该压栈序列的弹出序列。
class Solution {
public boolean validateStackSequences(int[] pushed, int[] popped) {
Stack<Integer> stack = new Stack<>();
int index = 0;
for(int i=0;i<pushed.length;i++){
stack.push(pushed[i]);
while (!stack.isEmpty() && stack.peek()==popped[index] && index<popped.length){
stack.pop();
index++;
}
}
return stack.isEmpty();
}
}
剑指 Offer 32 - I. 从上到下打印二叉树
从上到下打印出二叉树的每个节点,同一层的节点按照从左到右的顺序打印。
class Solution {
ArrayList<Integer> list = new ArrayList<>();
public int[] levelOrder(TreeNode root) {
if(root==null) return new int[0];
LinkedList<TreeNode> queue = new LinkedList<>();
queue.add(root);
while (!queue.isEmpty()){
TreeNode node = queue.pop();
list.add(node.val);
if(node.left!=null) queue.add(node.left);
if(node.right!=null) queue.add(node.right);
}
int[] arr = new int[list.size()];
for(int i=0;i<arr.length;i++){
arr[i] = list.get(i);
}
return arr;
}
}
剑指 Offer 32 - II. 从上到下打印二叉树 II
从上到下按层打印二叉树,同一层的节点按从左到右的顺序打印,每一层打印到一行。
class Solution {
List<List<Integer>> res = new ArrayList<>();
public List<List<Integer>> levelOrder(TreeNode root) {
if(root==null) return res;
LinkedList<TreeNode> queue = new LinkedList<>();
queue.add(root);
while (!queue.isEmpty()){
ArrayList<Integer> list = new ArrayList<>();
int len = queue.size();
for(int i=0;i<len;i++){
TreeNode node = queue.pop();
list.add(node.val);
if(node.left!=null) queue.add(node.left);
if(node.right!=null) queue.add(node.right);
}
res.add(list);
}
return res;
}
}
剑指 Offer 32 - III. 从上到下打印二叉树 III
请实现一个函数按照之字形顺序打印二叉树,即第一行按照从左到右的顺序打印,第二层按照从右到左的顺序打印,第三行再按照从左到右的顺序打印,其他行以此类推。
class Solution {
List<List<Integer>> res = new ArrayList<>();
public List<List<Integer>> levelOrder(TreeNode root) {
if(root==null) return res;
LinkedList<TreeNode> queue = new LinkedList<>();
queue.add(root);
int level = 1;
while (!queue.isEmpty()){
ArrayList<Integer> list = new ArrayList<>();
int len = queue.size();
for(int i=0;i<len;i++){
TreeNode node = queue.pop();
if(level%2==1){
list.add(node.val);
}else{
list.add(0,node.val);
}
if(node.left!=null) queue.add(node.left);
if(node.right!=null) queue.add(node.right);
}
level++;
res.add(list);
}
return res;
}
}