题号 | 题目名称 |
---|---|
1 | 二维数组中的查找 |
2 | 替换空格 |
3 | 从尾到头打印链表 |
4 | 重建二叉树 |
5 | 用两个栈实现队列 |
6 | 旋转数组的最小数字 |
7 | 斐波那契数列 |
8 | 跳台阶 |
9 | 变态跳台阶 |
10 | 矩形覆盖 |
在一个二维数组中(每个一维数组的长度相同),每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。
解法一: 我们可以利用该二维数组的性质:每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。也就是说,对于数组左下角的值 m,m 是该行最小的数,同时也是该列最大的数。我们只需要每次将 m 和目标值 target 进行比较:
1、当 m < target 时,由于 m 是当前列的最大元素,想要得到更大的数只能对列进行右移;
2、当 m > target 时,由于 m 是当前行的最小元素,想要得到更小的数只能对行进行上移;
3、当 m = target 时,找到该值,返回 true。
用某行最小或某列最大与 target 比较,每次可剔除一整行或一整列。故时间复杂度是O(m+n),使用两层循环暴力破解需要时间复杂度为O(n²)。
解法一:
public boolean Find(int target, int [][] array) {
if (array == null || array.length == 0) return false;
if (array[0].length == 0) return false;
int row = array.length - 1;
int col = 0;
do {
if (array[row][col] < target) {
col++;
} else if (array[row][col] > target) {
row--;
} else {
return true;
}
} while (row >= 0 && col < array[0].length);
return false;
}
请实现一个函数,将一个字符串中的每个空格替换成“%20”。例如,当字符串为We Are Happy.则经过替换之后的字符串为We%20Are%20Happy。
解法一: 用Java自带的替换函数,str.toString().replace(" “,”%20")。
解法二: 在当前字符串上进行替换。
1、先计算替换后的字符串需要多大的空间,并对原字符串空间进行扩容;
2、从后往前替换字符串的话,每个字符串只需要移动一次;
3、如果从前往后,每个字符串需要多次移动,效率较低。
解法三: 开辟一个新的字符串。与解法二基本一致,只是不需要对字符进行移动,而是将需要替换的字符直接添加到新的字符串上,需要整个字符串的额外空间。
解法一:
public String replaceSpace(StringBuffer str) {
if (str == null || str.length() == 0) return "";
return str.toString().replace(" ","%20");
}
解法二:
public static String replaceSpace1(StringBuffer str) {
if (str == null || str.length() == 0) return "";
int count = 0;
for (int i = 0; i < str.length(); i++) {
if (str.charAt(i) == ' ') {
count++;
}
}
int oldLen = str.length();
str.setLength(oldLen + 2 * count);
for (int i = oldLen - 1; i >= 0; i--) {
if (str.charAt(i) != ' ') {
str.setCharAt(i + 2 * count, str.charAt(i));
} else {
str.setCharAt(i + 2 * count, '0');
str.setCharAt(i + 2 * count - 1, '2');
str.setCharAt(i + 2 * count - 2, '%');
count--;
}
}
return str.toString();
}
解法三:
public String replaceSpace(StringBuffer str) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < str.length(); i++) {
char c = str.charAt(i);
if (c == ' ') {
sb.append("%20");
} else {
sb.append(c);
}
}
return sb.toString();
}
输入一个链表,按链表从尾到头的顺序返回一个ArrayList。
解法一: 依次遍历链表并将每个链表元素插入到ArrayList中,最后使用Collection.reverse对ArrayList进行翻转并返回。
解法二: ArrayList中有个方法是add(index,value),可以指定index位置插入value值。依次遍历链表并将每个链表元素插入到list的0位置,最后返回ArrayList即可得到逆序链表。
解法三: 利用递归,借助系统的栈将元素压栈,依次出栈并将元素添加至ArrayList中即可完成逆序。
解法一:
public static ArrayList<Integer> printListFromTailToHead(ListNode listNode) {
ListNode head = listNode;
ArrayList<Integer> list = new ArrayList<>();
while (head != null) {
list.add(head.val);
head = head.next;
}
Collections.reverse(list);
return list;
}
解法二:
public static ArrayList<Integer> printListFromTailToHead(ListNode listNode) {
ArrayList<Integer> list = new ArrayList<>();
ListNode tmp = listNode;
while(tmp != null){
list.add(0, tmp.val);
tmp = tmp.next;
}
return list;
}
解法三:
ArrayList<Integer> list = new ArrayList();
public ArrayList<Integer> printListFromTailToHead(ListNode listNode) {
if (listNode != null) {
printListFromTailToHead(listNode.next);
list.add(listNode.val);
}
return list;
}
输入某二叉树的前序遍历和中序遍历的结果,请重建出该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。例如输入前序遍历序列{1,2,4,7,3,5,6,8}和中序遍历序列{4,7,2,1,5,3,8,6},则重建二叉树并返回。
解法一:
根据中序遍历和前序遍历可以确定二叉树,具体过程为:
例如对于前序遍历序列{1,2,4,7,3,5,6,8}和中序遍历序列{4,7,2,1,5,3,8,6}而言,我们的步骤是:
解法一:
public TreeNode reConstructBinaryTree(int [] pre, int [] in) {
if (pre.length == 0 || in.length == 0) {
return null;
}
TreeNode h = new TreeNode(pre[0]);
int index = -1;
for (int i = 0; i < in.length; i++) {
if (in[i] == pre[0]) {
index = i;
}
}
int[] in1 = Arrays.copyOfRange(in, 0, index);
int[] in2 = Arrays.copyOfRange(in, index + 1, in.length);
int[] p1 = Arrays.copyOfRange(pre, 1, in1.length + 1);
int[] p2 = Arrays.copyOfRange(pre, in1.length + 1, pre.length);
h.left = reConstructBinaryTree(p1, in1);
h.right = reConstructBinaryTree(p2, in2);
return h;
}
用两个栈来实现一个队列,完成队列的Push和Pop操作。 队列中的元素为int类型。
解法一: 定义两个栈,栈A和栈B。栈A只负责正常的push;当栈B有元素时则直接pop,当栈B没有元素时,将栈A中的元素全部、一次性地pop到栈B,然后对栈B进行pop。
解法一:
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 (stack2.empty()) {
while (!stack1.empty()) {
stack2.push(stack1.pop());
}
}
if (!stack2.empty()) {
return stack2.pop();
} else {
return -1;
}
}
}
把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。输入一个非递减排序的数组的一个旋转,输出旋转数组的最小元素。例如数组{3,4,5,1,2}为{1,2,3,4,5}的一个旋转,该数组的最小值为1。
NOTE:给出的所有元素都大于0,若数组大小为0,请返回0。
解法一: 一次遍历。数组在发生旋转后,以最小元素为中心的左右两端数组都是升序的,因此我们可以对数组进行遍历,当某个元素比它前一个元素小,则说明该元素是数组中的最小元素。
解法二: 二分查找。二分查找用于查找有序的数组中的值,题目所给数组在两段范围内有序,我们可以将给定数组分为两种情况:
当数组如情况 1,有个鲜明的特征,即数组左边元素 < 数组右边元素,这时我们直接返回首元素即可;
当数组如情况 2,此时有三种可能找到最小值:
再讨论每次二分查找时范围的变化,由于情况数组的情况 1 能直接找到最小值,需要变化范围的肯定是情况 2:
解法一: O(n)
public int minNumberInRotateArray(int [] array) {
if (array.length == 0) return 0;
int index = 0;
for (int i = 1; i < array.length; i++) {
if (array[i] < array[i - 1]) {
index = i;
break;
}
}
return array[index];
}
解法二: O(logn)
public int minNumberInRotateArray(int [] array) {
if (array.length == 1) return array[0];
int l = 0;
int r = array.length - 1;
while (l < r) {
int m = l + ((r - l) >> 1);
if (array[l] < array[r]) {
return array[l];
}
if (array[m] > array[m + 1]) {
return array[m + 1];
}
if (array[m] < array[m - 1]) {
return array[m];
}
if (array[m] > array[0]) {
l = m + 1;
} else {
r = m - 1;
}
}
return 0;
}
大家都知道斐波那契数列F(n)=F(n-1)+F(n-2),现在要求输入一个整数n,请你输出斐波那契数列的第n项(从0开始,第0项为0,第1项为1)。n<=39.
解法一: 递归。根据F(n)=F(n-1)+F(n-2)递归调用即可。
解法二: 动态规划。可以选择一个长度为n的数组用于保存每次计算的值,遍历数组,根据数组之前的元素得到当前元素,在该情况下空间复杂度为O(n)。由于在遍历过程中,实际上只用到n-1和n-2两个位置的值,因此可以只使用两个变量对历史的计算结果进行存储,将空间复杂度优化到O(1)。
解法一: O(2^n)
public int Fibonacci(int n) {
if (n <= 1) return n;
return Fibonacci(n-1) + Fibonacci(n-2);
}
解法一: O(n)
public int Fibonacci(int n) {
if (n == 0) return 0;
if (n <= 2) return 1;
int pre1 = 1;
int pre2 = 1;
int res = 0;
for (int i = 3; i <= n; i++) {
res = pre1 + pre2;
pre1 = pre2;
pre2 = res;
}
return res;
}
一只青蛙一次可以跳上1级台阶,也可以跳上2级。求该青蛙跳上一个n级的台阶总共有多少种跳法(先后次序不同算不同的结果)。
解法一: 动态规划。斐波那契数列的变种,上一题的优化解法需要三个额外变量,该解法只需要两个额外变量,sum用来存储当前值,pre用来存储前一个值,但是空间复杂度仍为O(1)。
解法一: O(n)
public int JumpFloor(int target) {
if (target < 2) return 1;
int pre = 1;
int sum = 1;
for (int i = 2; i <= target; i++) {
sum = sum + pre;
pre = sum - pre;
}
return sum;
}
一只青蛙一次可以跳上1级台阶,也可以跳上2级……它也可以跳上n级。求该青蛙跳上一个n级的台阶总共有多少种跳法。
解法一: 动态规划。斐波那契数列的变种。由于青蛙每次可以跳n级台阶,因此青蛙跳上n级台阶的跳法为f(n)=f(n-1)+f(n-2)+……f(1)+f(0)。经过分析,发现结果呈规律性,f(n)以2的n次幂形式增长,f(0)=1,f(1)=1,f(2)=2…从而可以归纳得到计算表达式:f(n)=2^(n-1)。
解法一: O(1)
public int JumpFloorII(int target) {
return target == 0 ? 1 : (int) Math.pow(2, target - 1);
}
我们可以用2*1的小矩形横着或者竖着去覆盖更大的矩形。请问用n个2*1的小矩形无重叠地覆盖一个2*n的大矩形,总共有多少种方法?
解法一: 动态规划。斐波那契数列的变种。其实和第9题是一样的,f(n)=f(n-1)+f(n-2)。因为每次多加1*2的矩形时,无非就是f(n-1)种然后加上一条竖着的矩阵,或者f(n-2)种然后加上两条横着的矩阵。在该解法中使用的是递归方法求解,时间复杂度极高。在这仅仅是给出递归解的实现,建议使用时间复杂度为O(n)、空间复杂度为O(1)的最优解法(参考第7-9题)。
解法一: O(2^n)
public int RectCover(int target) {
if (target == 0) return 0;
if (target == 1) return 1;
if (target == 2) return 2;
return RectCover(target - 1) + RectCover(target - 2);
}