剑指Offer——编程题的Java实现(更新完毕……)

本文详细解析了《剑指Offer》中涉及的编程题目,包括二维数组查找、字符串处理、链表操作、树结构问题、栈与队列的应用、递归与动态规划、数组和链表中的各种操作,以及数据结构与算法的实践。内容涵盖了从基本的数组排序到复杂的二叉树遍历、链表环的检测与删除、数据流中的中位数计算等。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

 

目录

二维数组中的查找

替换空格 

从尾到头打印链表

重建二叉树

用两个栈实现队列

用两个队列实现一个栈

旋转数组的最小数字

 斐波那契数列

跳台阶

变态跳台阶

矩形覆盖

 二进制中1的个数

数值的整数次方

调整数组顺序使奇数位于偶数前面

相对位置发生变化的解法

链表中倒数第k个结点

反转链表

合并两个排序的链表

树的子结构

顺时针打印矩阵

二叉树的镜像

包含min函数的栈

栈的压入、弹出序列

二叉搜索树的后序遍历序列

二叉树中和为某一值的路径

复杂链表的复制

连续子数组的最大和

字符串的排列

二叉树与双向链表

字符串的组合

数组中出现次数超过一半的数字

把数组排成最小的数

第一个只出现一次的字符

数组中的逆序对

两个链表的第一个公共结点

数字在排序数组中出现的次数

二叉树的深度及判断是否为平衡二叉树

数组中只出现一次的数字

使用map

使用异或

和为S的连续正数序列

和为S的两个数字

左旋转字符串

翻转单词顺序列

扑克牌顺子

孩子们的游戏(圆圈中最后剩下的数)

约瑟夫环问题

递推公式

求1~n的和(不使用乘除)

不用加减乘除做加法

字符串转数字

数组中重复的数字

构建乘积数组

正则表达式匹配

字符流中第一个不重复的字符

链表中环的入口结点

删除链表中重复的结点

重复结点不保留

重复结点保留

表示数值的字符串

二叉树的下一个结点

对称的二叉树

把二叉树打印成多行

之字形打印二叉树

序列化二叉树

二叉搜索树的第k个结点

数据流中的中位数

滑动窗口的最大值


 

二维数组中的查找

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

 

       /*
	 * 思路 矩阵是有序的,从右上角来看,向左数字递减,向下数字递增, 
	 * 因此从右上角开始查找,当要查找数字比左下角数字大时。下移
	 * 要查找数字比左上角数字小时,左移
	 */
	public class Solution {
		public boolean Find(int[][] array, int target) {
			int len = array.length - 1;
			int i = 0;
			while ((len >= 0) && (i < array[0].length)) {
				if (array[len][i] > target) {
					len--;
				} else if (array[len][i] < target) {
					i++;
				} else {
					return true;
				}
			}
			return false;
		}
	}
  • 替换空格 

请实现一个函数,将一个字符串中的空格替换成“%20”。例如,当字符串为We Are Happy.则经过替换之后的字符串为We%20Are%20Happy。请实现一个函数,将一个字符串中的空格替换成“%20”。例如,当字符串为We Are Happy.则经过替换之后的字符串为We%20Are%20Happy。

 public class Solution {  
            // 从后往前,先计算需要多少空间,然后从后往前移动,则每个字符只为移动一次.  
            public String replaceSpace(StringBuffer str) {  
                if (str == null) {  
                    return null;  
                }  
                int blankNum = 0;  
                int length = str.length();  
                int newLength = 0;  
                for (int i = 0; i < length; i++) {  
                    if (str.charAt(i) == ' ') {  
                        blankNum++;  
                    }  
                }  
                newLength = length + 2 * blankNum; // 替换后的字符串长度  
                char[] newChars = new char[newLength];// 新的字符数组  
                int index = newLength - 1;  
                for (int i = length - 1; i >= 0; i--) {  
                    if (str.charAt(i) == ' ') {  
                        newChars[index--] = '0';  
                        newChars[index--] = '2';  
                        newChars[index--] = '%';  
                    } else {  
                        newChars[index--] = str.charAt(i);  
                    }  
                }  
                return new String(newChars);  
            }  
        }  
    public class Solution {  
            //借助StringBuffer  
            public String replaceSpace(StringBuffer str) {  
                StringBuffer sb = new StringBuffer();  
                for (int i = 0; i < str.length(); i++) {  
                    if (str.charAt(i) == ' ') {  
                        sb.append("%20");  
                    } else {  
                        sb.append(str.charAt(i));  
                    }  
                }  
                return sb.toString();  
            }  
        }  
  • 从尾到头打印链表

输入一个链表,从尾到头打印链表每个节点的值。

链表结点定义:

public class ListNode {
	int val;
	ListNode next = null;

	ListNode(int val) {
		this.val = val;
	}
}

使用递归:
 
import java.util.ArrayList;
	public class Solution {
		ArrayList<Integer> arrayList = new ArrayList<Integer>();
		//使用递归实现
		public ArrayList<Integer> printListFromTailToHead(ListNode listNode) {
			if (listNode != null) {
				printListFromTailToHead(listNode.next);
				arrayList.add(listNode.val);
			}
			return arrayList;
		}
	}

使用栈的后进先出
import java.util.ArrayList;
import java.util.Stack;

public class Solution {

	public ArrayList<Integer> printListFromTailToHead(ListNode listNode) {

		Stack<Integer> stack = new Stack<>();
		while (listNode != null) {
			stack.push(listNode.val);
			listNode = listNode.next;
		}

		ArrayList<Integer> list = new ArrayList<>();
		while (!stack.isEmpty()) {
			list.add(stack.pop());
		}
		return list;
	}
}
  • 重建二叉树

输入某二叉树的前序遍历和中序遍历的结果,请重建出该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。例如输入前序遍历序列{1,2,4,7,3,5,6,8}和中序遍历序列{4,7,2,1,5,3,8,6},则重建二叉树并返回。

/**
	 * Definition for binary tree
	 * public class TreeNode {
	 *     int val;
	 *     TreeNode left;
	 *     TreeNode right;
	 *     TreeNode(int x) { val = x; }
	 * }
	 */
	public class Solution {
		public TreeNode reConstructBinaryTree(int[] pre, int[] in) {
			TreeNode root = reConstructBinaryTree(pre, 0, pre.length - 1, in, 0, in.length - 1);
			return root;
		}

		// 前序遍历{1,2,4,7,3,5,6,8}和中序遍历序列{4,7,2,1,5,3,8,6}
		private TreeNode reConstructBinaryTree(int[] pre, int startPre, int endPre, int[] in, int startIn, int endIn) {

			if (startPre > endPre || startIn > endIn)
				return null;
			TreeNode root = new TreeNode(pre[startPre]);

			for (int i = startIn; i <= endIn; i++)
				if (in[i] == pre[startPre]) {
					root.left = reConstructBinaryTree(pre, startPre + 1, startPre + i - startIn, in, startIn, i - 1);
					root.right = reConstructBinaryTree(pre, i - startIn + startPre + 1, endPre, in, i + 1, endIn);
				}

			return root;
		}
	}

用两个栈实现队列

用两个栈来实现一个队列,完成队列的Push和Pop操作。 队列中的元素为int类型。

有两个栈,栈1和栈2.当入栈的时候,我们将它全放进栈1中,当需要出栈的时候,我们将栈1出栈到栈2中,然后再将栈2依次出栈。所以入栈的时候,思路很简单;当需要出栈的时候,我们用API提供的方法while(stack1.isEmpty())来将所有栈1的元素压入栈2中,然后将栈2弹出就可以.

 

import java.util.Stack;

public class Solution {
	Stack<Integer> stack1 = new Stack<Integer>();
	Stack<Integer> stack2 = new Stack<Integer>();

	public void push(int node) {
		stack1.push(node);
	}

	public int pop() {
		if (stack1.empty() && stack2.empty()) {
			throw new RuntimeException("Queue is empty!");
		}
		if (stack2.empty()) {
			while (!stack1.empty()) {
				stack2.push(stack1.pop());
			}
		}
		return stack2.pop();
	}

}

用两个队列实现一个栈

移步

http://blog.youkuaiyun.com/mine_song/article/details/63322097

旋转数组的最小数字

把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。输入一个非递减排序的数组的一个旋转,输出旋转数组的最小元素。例如数组{3,4,5,1,2}为{1,2,3,4,5}的一个旋转,该数组的最小值为1。
NOTE:给出的所有元素都大于0,若数组大小为0,请返回0。

使用二分查找:

 

public class Solution {
		public int minNumberInRotateArray(int[] array) {
			if (array == null || array.length == 0)
				return 0;
			int low = 0;
			int high = array.length - 1;
			while (low < high) {
				int mid = low + (high - low) / 2;
				if (array[mid] > array[high]) {
					low = mid + 1;
					// high = high - 1;可以避免low,high,mid相等的找不到最小值情况。
					// int[] array={1,0,1,1,1};
				} else if (array[mid] == array[high]) {
					high = high - 1;
				} else {
					high = mid;
				}
			}
			return array[low];
		}
	}

首先数组长度为零时,返回零,因为测试要求这样。然后有一个特殊情况是没有旋转,那么返回array[0],其次一般情况while一直循环,直到后面的数 < 前面的数停止,这个数就是我们要找的。

 

 

public class Solution {
		public int minNumberInRotateArray(int[] array) {
			if (array.length == 0)
				return 0;
			// 避免i+1越界,i要小于array.length - 1
			for (int i = 0; i < array.length - 1; i++) {
				if (array[i] > array[i + 1])
					return array[i + 1];
			}
			// 所有元素相等时候或者未旋转,返回array[0]
			return array[0];
		}
	}

 

 斐波那契数列

 

大家都知道斐波那契数列,现在要求输入一个整数n,请你输出斐波那契数列的第n项。n<=39

 

public class Solution {

		public int Fibonacci(int n) {
			// 方法:用递归,系统会让一个超大的n来让StackOverflow,所以递归就不考虑了
			// 使用迭代法,用fn1和fn2保存计算过程中的结果,并复用起来
			int fn1 = 1;
			int fn2 = 1;// 考虑出错情况
			int res = 0;
			if (n <= 0) {
				return 0;
			} // 第一和第二个数直接返回
			if (n == 1 || n == 2) {
				return 1;
			}
			for (int i = 3; i <= n; i++) {
				res = fn1 + fn2;
				fn2 = fn1;
				fn1 = res;
			}
			return res;
		}

	}

 

跳台阶

一只青蛙一次可以跳上1级台阶,也可以跳上2级。求该青蛙跳上一个n级的台阶总共有多少种跳法。

 

public class Solution {
		public int JumpFloor(int target) {

			int fn1 = 1;
			int fn2 = 2;
			int res = 0;
			if (target <= 0) {
				return 0;
			}
			if (target == 1) {
				return 1;
			}
			if (target == 2) {
				return 2;
			}
			for (int i = 3; i <= target; i++) {
				res = fn1 + fn2;
				fn1 = fn2;
				fn2 = res;
			}
			return res;
		}
	}

递归

 

对于N级台阶,可以从N-1级和N-2级上来,所以JumpFloor(N) =  JumpFloor(N-1)+JumpFloor(N-2)
N=1时,只有一种
N=2时,有两种:一次2级;两次1级

 

 

 

 

public class Solution {

		public int JumpFloor(int target) {
			int result = 0;
			if (target > 0) {
				if (target <= 2)
					return target;
				else
					return result = JumpFloor(target - 1) + JumpFloor(target - 2);
			}
			return result;
		}

变态跳台阶

一只青蛙一次可以跳上1级台阶,也可以跳上2级……它也可以跳上n级。求该青蛙跳上一个n级的台阶总共有多少种跳法。

:

非递归:

 

	public class Solution {
		public int JumpFloorII(int target) {
			int jumpFlo = 1;
			while (--target > 0) {
				jumpFlo *= 2;
			}
			return jumpFlo;

		}
	}


2^(n-1)可以用位移操作进行

 

 

	public class Solution {
		public int JumpFloorII(int target) {
			return  1<<--target;
		}
        }


使用递归:

 

 

public class Solution {
		public int JumpFloorII(int target) {
			if (target < 0) {
				return 0;
			} else if (target == 1) {
				return 1;
			} else {
				return 2 * JumpFloorII(target - 1);
			}
		}
	}

 

矩形覆盖

 

我们可以用2*1的小矩形横着或者竖着去覆盖更大的矩形。请问用n个2*1的小矩形无重叠地覆盖一个2*n的大矩形,总共有多少种方法?

同上述青蛙跳台阶

 二进制中1的个数

    输入一个整数,输出该数二进制表示中1的个数。其中负数用补码表示。

 如果一个整数不为0,那么这个整数至少有一位是1。如果我们把这个整数减1,那么原来处在整数最右边的1就会变为0,原来在1后面的所有的0都会变成1(如果最右边的1后面还有0的话)。其余所有位将不会受到影响。

  举个例子:一个二进制数1100,从右边数起第三位是处于最右边的一个1。减去1后,第三位变成0,它后面的两位0变成了1,而前面的1保持不变,因此得到的结果是1011.我们发现减1的结果是把最右边的一个1开始的所有位都取反了。这个时候如果我们再把原来的整数和减去1之后的结果做与运算,从原来整数最右边一个1那一位开始所有位都会变成0。如1100&1011=1000.也就是说,把一个整数减去1,再和原整数做与运算,会把该整数最右边一个1变成0.那么一个整数的二进制有多少个1,就可以进行多少次这样的操作。

 

public class Solution {
	public int NumberOf1(int n) {
		int num = 0;
		while (n != 0) {
			n = n & (n - 1);
			num++;
		}
		return num;
	}
}

 

数值的整数次方

 

给定一个double类型的浮点数base和int类型的整数exponent。求base的exponent次方

 


public class Solution {
	//时间复杂度O(n)
	public double Power(double base, int exponent) {
		int n = Math.abs(exponent);
		if (n == 0)
			return 1;
		if (n == 1)
			return base;
		//以上两个if判断可省。for循环中判断
		double result = 1.0;
		for (int i = 0; i < n; i++) {
			result *= base;
		}
		if (exponent < 0) {
			result = 1 / result;
		}
		return result;
	}
}

使用递归,时间复杂度O(logn)
当n为偶数,a^n =(a^n/2)*(a^n/2)
当n为奇数,a^n = a^[(n-1)/2] * a^[(n-1)/2] * a
举例
2^11 = 2^1 * 2^2 * 2^8
2^1011 = 2^0001 * 2^0010 * 2^1000

 

 

public class Solution {
	// 时间复杂度O(lgn)
	public double power(double base, int exponent) {
		int n = Math.abs(exponent);
		double result = 0.0;
		if (n == 0)
			return 1.0;
		if (n == 1)
			return base;
		result = power(base, n >> 1);
		result *= result;
		// 如果指数n为奇数,则要再乘一次底数base
		// 最后一位是1,与1相与得1,是奇数
		if ((n & 1) == 1)
			result *= base;
		// 如果指数为负数,则应该求result的倒数
		if (exponent < 0)
			result = 1 / result;
		return result;
	}
}

调整数组顺序使奇数位于偶数前面

输入一个整数数组,实现一个函数来调整该数组中数字的顺序,使得所有的奇数位于数组的前半部分,所有的偶数位于位于数组的后半部分,并保证奇数和奇数,偶数和偶数之间的相对位置不变。

1.使用冒泡排序,前偶后奇就交换

 

public void reOrderArray(int[] array) {
		for (int i = 0; i < array.length - 1; i++) {
			for (int j = 0; j < array.length - 1 - i; j++) {
				// 前偶后奇数就交换
				if ((array[j] & 1) == 0 && (array[j + 1] & 1) == 1) {
					array[j] = array[j] ^ array[j + 1];
					array[j + 1] = array[j] ^ array[j + 1];
					array[j] = array[j] ^ array[j + 1];
				}
			}
		}
	}


2.空间换时间,使用额外数组。

 

 

public void reOrderArray(int[] array) {
		int[] newArr = new int[array.length];
		//newArr的下标计数器
		int j = 0;
		for (int i = 0; i < array.length; i++)
			if ((array[i] & 1) == 1) {
				newArr[j] = array[i];
				j++;
			}
		for (int i = 0; i < array.length; i++)
			if ((array[i] & 1) == 0) {
				newArr[j] = array[i];
				j++;
			}
		for (int i = 0; i < array.length; i++)
			array[i] = newArr[i];
	}

相对位置发生变化的解法

 

public class Solution {

	public void reOrderArray(int[] array) {
		if (array == null)
			return;
		int begin = 0;
		int end = array.length - 1;
		while (begin <= end) {
			while (begin <= end && ((array[begin] & 1) == 1))
				begin++;
			while (begin <= end && ((array[end] & 1) == 0))
				end--;
			if (begin <= end) {
				array[begin] = array[begin] ^ array[end];
				array[end] = array[begin] ^ array[end];
				array[begin] = array[begin] ^ array[end];
			}
		}
	}
}

链表中倒数第k个结点

输入一个链表,输出该链表中倒数第k个结点。

快慢指针,让快指针先走k步,然后慢指针开始走,若快指针走到末尾(为null),就是慢指针指向的就是倒数第k个结点

 

public class ListNode {
	int val;
	ListNode next = null;

	ListNode(int val) {
		this.val = val;
	}
}

 

 

public class Solution {
	public ListNode FindKthToTail(ListNode head, int k) {
		ListNode front = head;
		int i = 0;
		for (; front != null && i < k; i++) {
			front = front.next;

		}
		// 如果k大于链表的长度或者k小于0,返回null;
		if (i != k)
			return null;
		ListNode behind = head;
		while (front != null) {
			front = front.next;
			behind = behind.next;
		}
		// 若k等于0,则behind为null
		return behind;
	}
}

反转链表

输入一个链表,反转链表后,输出链表的所有元素。

 

public class ListNode {
	int val;
	ListNode next = null;

	ListNode(int val) {
		this.val = val;
	}
}

 

 

public class Solution {
	public ListNode ReverseList1(ListNode head) {
		if (head == null)
			return null;
		// head为当前节点,如果当前节点为空的话,那就什么也不做,直接返回null;
		ListNode pre = null;
		ListNode next = null;
		// 当前节点是head,pre为当前节点的前一节点,next为当前节点的下一节点
		// 需要pre和next的目的是让当前节点从pre->head->next1->next2变成pre<-head next1->next2
		// 即pre让节点可以反转所指方向,但反转之后如果不用next节点保存next1节点的话,此单链表就此断开了
		// 所以需要用到pre和next两个节点
		// 1->2->3->4->5
		// 1<-2<-3 4->5
		while (head != null) {
			// 做循环,如果当前节点不为空的话,始终执行此循环,此循环的目的就是让当前节点从指向next到指向pre 
			// 如此就可以做到反转链表的效果
			// 先用next保存head的下一个节点的信息,保证单链表不会因为失去head节点的原next节点而就此断裂
			next = head.next;
			// 保存完next,就可以让head从指向next变成指向pre了,代码如下
			head.next = pre;
			// head指向pre后,就继续依次反转下一个节点
			// 让pre,head,next依次向后移动一个节点,继续下一次的指针反转
			pre = head;
			head = next;
		}
		// 如果head为null的时候,pre就为最后一个节点了,但是链表已经反转完毕,pre就是反转后链表的第一个节点
		// 直接输出pre就是我们想要得到的反转后的链表
		return pre;
	}
}

 

public class Solution {
	public ListNode ReverseList(ListNode head) {
		if (head == null)
			return null;
		ListNode newhead = null;
		ListNode pre = null;
		ListNode pNext = null;
		ListNode pNode = head;
		while (pNode != null) {
			pNext = pNode.next;
			//最后一个头结点赋值给newHead
			if (pNext == null)
				newhead = pNode;
			pNode.next = pre;
			pre = pNode;
			pNode = pNext;
		}
		return newhead;

	}
}

 

合并两个排序的链表

输入两个单调递增的链表,输出两个链表合成后的链表,当然我们需要合成后的链表满足单调不减规则。
    比较两个链表的首结点,哪个小的的结点则合并到第三个链表尾结点,并向前移动一个结点。
    步骤一结果会有一个链表先遍历结束,或者没有
    第三个链表尾结点指向剩余未遍历结束的链表
    返回第三个链表首结点

public ListNode Merge(ListNode list1, ListNode list2) {
		if (list1 == null)
			return list2;
		if (list2 == null)
			return list1;
		//新建一个头节点,用来存合并的链表。
		ListNode newList = new ListNode(-1);
		ListNode temp = newList;
		while (list1 != null && list2 != null) {
			if (list1.val <= list2.val) {
				temp.next = list1;
				list1 = list1.next;
				temp = temp.next;

			} else {
				temp.next = list2;
				list2 = list2.next;
				temp = temp.next;
			}

		}
		//把未结束的链表连接到合并后的链表尾部
		if (list1 != null) {
			temp.next = list1;
		}
		if (list2 != null) {
			temp.next = list2;
		}
		return newList.next;
	}

递归:

 

 

public ListNode Merge(ListNode list1, ListNode list2) {
		if (list1 == null)
			return list2;
		if (list2 == null)
			return list1;
		if (list1.val <= list2.val) {
			list1.next = Merge(list1.next, list2);
			return list1;
		} else {
			list2.next = Merge(list1, list2.next);
			return list2;
		}
	}

 

树的子结构

输入两棵二叉树A,B,判断B是不是A的子结构。(ps:我们约定空树不是任意一个树的子结构)

 思路:参考剑指offer
   1、首先设置标志位result = false,因为一旦匹配成功result就设为true,剩下的代码不会执行,如果匹配不成功,默认返回false

   2、递归思想,如果根节点相同则递归调用DoesTree1HaveTree2(),如果根节点不相同,则判断tree1的左子树和tree2是否相同,再判断右子树和tree2是否相同

3、注意null的条件,HasSubTree中,如果两棵树都不为空才进行判断,DoesTree1HasTree2中ÿ

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值