0、两数之和
给一个整数数组,找到两个数使得他们的和等于一个给定的数target。
//输入:nums = [2,7,11,15], target = 9
//输出:[0,1]
public class TwoSum {
//方法一:两层for循环暴力解法
//方法二:利用哈希表
public int[] twoSum(int[] numbers, int target) {
int[] a = new int[2];
HashMap<Integer, Integer> map = new HashMap<>();
for (int i = 0; i < numbers.length; i++){
if (map.containsKey(numbers[i])){
a[0] = map.get(numbers[i]);
a[1] = i + 1;
return a;
}
//存储差值为key,下标为value(题目要求从1开始故+1)
map.put(target - numbers[i], i + 1);
}
return a;
}
}
1、两数相加
给你两个 非空 的链表,表示两个非负的整数。它们每位数字都是按照 逆序 的方式存储的,并且每个节点只能存储 一位 数字。
请你将两个数相加,并以相同形式返回一个表示和的链表。
输入:l1 = [2,4,3], l2 = [5,6,4]
输出:[7,0,8]
正常的两数相加是是从个位即右边开始加,这里的两数相加是从左边开始加,然后进位到右边。所以刚好遍历两个链表,将对应位置的数相加,如果两个链表的长度不同,则可以认为长度短的链表的后面有若干个 0。
class Solution {
public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
ListNode pre = new ListNode(0); //作为头节点,最后返回
ListNode cur = pre;
int carry = 0;
//只要有一个链表不为0就继续循环
while(l1 != null || l2 != null) {
//如果某个链表遍历完了,则赋值为0
int x = (l1 == null ? 0 : l1.val);
int y = (l2 == null ? 0 : l2.val);
int sum = x + y + carry;
carry = sum / 10; //取整作为进位
int curval = sum % 10; //取余作为当前位
cur.next = new ListNode(curval); //用当前位的值new一个节点
cur = cur.next;
if(l1 != null)
l1 = l1.next;
if(l2 != null)
l2 = l2.next;
}
//两个链表都循环完了,如果进位不为0,则new一个新节点
if(carry == 1) {
cur.next = new ListNode(carry);
}
return pre.next;
}
}
2、将两个有序链表合并为一个新的有序链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。
输入:1->2->4, 1->3->4
输出:1->1->2->3->4->4
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
class Solution {
public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
// 定义一个链表,当前节点值为-1,这个初始节点值我们是不需要的。所以最后返回的时候是返回dummy.next
ListNode prev = new ListNode(-1);
//prev不断的往后移动。prev只能代表当前位置开始的链表。只有头指针能代表整个新链表,所以最后需要从链表的头节点开始返回,所以这里需要保留头指针的位置
ListNode dummy = prev;
while (l1 != null && l2 != null) {
if (l1.val <= l2.val) {
prev.next = l1;
l1 = l1.next;
} else {
prev.next = l2;
l2 = l2.next;
}
prev = prev.next;
}
//上面当l1或者l2其中一个为null,就会跳出循环了。但是我们还需要加上不为空的那个链表剩下的元素
if (l1 != null) {
prev.next = l1;
}
if (l2 != null) {
prev.next = l2;
}
//能代表整个链表的是头指针,
return dummy.next;
}
}
3、给定一个排序数组,你需要在原地删除重复出现的元素,使得每个元素只出现一次,返回移除后数组的新长度。
不要使用额外的数组空间,你必须在原地修改输入数组并在使用 O(1) 额外空间的条件下完成。
示例 1:
给定数组 nums = [1,1,2],
函数应该返回新的长度 2, 并且原数组 nums 的前两个元素被修改为 1, 2。
首先注意数组是有序的,那么重复的元素一定会相邻。
要求删除重复元素,实际上就是将不重复的元素移到数组的左侧。
注意原数组长度是不变的。只是前面部分位置的元素被修改了。后面位置的元素其实如果没被重新赋值的话是不变的
public int removeDuplicates(int[] nums) {
if (nums.length == 0) return 0;
//快慢双指针:i是慢指针,只有在满足某种情况下才会++;j是快指针
int i = 0;
for (int j = 1; j < nums.length; j++) {
if (nums[j] != nums[i]) {
//完成不重复的元素统计
i++;
//如果比较的两个元素不一致就重新赋值。即不重复的元素就会赋值。重复的跳过不赋值。注意前面的位置不动即可
nums[i] = nums[j];
}
}
return i + 1;
}
4、在字符串S中找到子字符串T出现的位置,没有则返回-1
class Solution {
public int strStr(String S, String T) {
int n1 = S.length();
int n2 = T.length();
if (n1 < n2) return -1;
else if ( n2 == 0) return 0;
for (int i = 0; i < n1 - n2 + 1; i++ ){
if (S.substring(i, i+n2).equals(T)) return i;
}
return -1;
}
}
5、搜索插入位置
给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。
输入: [1,3,5,6], 5
输出: 2
class Solution:
def searchInsert(self, nums: List[int], target: int) -> int:
high = len(nums) - 1
low = 0
if target < nums[0]:
return 0
if target > nums[high]:
return high+1
while low <= high:
mid = (high+low)//2
if nums[mid] == target:
return mid
elif nums[mid] > target:
high = mid -1
elif nums[mid] < target:
low = mid + 1
return low # low 为刚好大于target的位置索引
# return high 则返回刚好小于target的位置索引
6、最大子序和
给定一个整数数组 nums ,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。
示例:
输入: [-2,1,-3,4,-1,2,1,-5,4],
输出: 6
解释: 连续子数组 [4,-1,2,1] 的和最大,为 6。
贪心法
class Solution {
public int maxSubArray(int[] nums) {
int n = nums.length;
int currSum = nums[0], maxSum = nums[0];
for(int i = 1; i < n; ++i) {
//如果nums[i]大于currSum + nums[i],也大于maxSum。那就可以从当前节点开始算最大和。因为前面的一串数字已经都比当前节点小了。
currSum = Math.max(nums[i], currSum + nums[i]);
maxSum = Math.max(maxSum, currSum);
}
return maxSum;
}
}
动态规划思想是希望连续的,也就是说上一个状态和下一个状态(自变量)之间有关系而且连续。
dp[i]:表示 nums 数组中前 i 个元素中的连续子数组的最大和,并且 dp[i] 与 dp[i - 1] 和 num[i] 均有关。
遍历 nums 数组,i ∈ [1, n),有两种情况:考虑 nums[i] 单独成为一段 (即直接从 nums[i] 位置重新开始找连续子数组的最大和) 还是将 nums[i] 加入到 f(i − 1) 对应的那一段,这取决于 nums[i] 和 f(i - 1) + nums[i] 的大小,希望获得一个比较大的。
若前 i - 1 个元素中的连续子数组的最大和加上当前 i 位置上的元素值,即dp[i - 1] + nums[i] > nums[i],则状态转移方程:dp[i] = dp[i - 1] + nums[i]
否则,状态转移方程:dp[i] = nums[i]
初始化:令 dp[0] = nums[0],当只有一个元素时,连续子数组的最大和就是它本身。
class Solution {
public int maxSubArray(int[] nums) {
int n = nums.length;
if (n == 1) return nums[0];
int[] dp = new int[n];
int max = nums[0];
dp[0] = nums[0];
for (int i=1;i<n;i++) {
dp[i] = Math.max(dp[i - 1] + nums[i], nums[i]);
max = Math.max(max, dp[i]);
}
return max;
}
}
7、二进制求和
给定两个二进制字符串,返回他们的和(用二进制表示)。
输入: a = “11”, b = “1”
输出: “100”
方法一:使用内置函数:先转换为10进制相加,再将和转换为二进制
存在问题:字符串长度太大时,不能将其转换为 Integer,Long 或者 BigInteger 类型。如33 位 1,不能转换为 Integer。
class Solution {
public String addBinary(String a, String b) {
return Integer.toBinaryString(Integer.parseInt(a, 2) + Integer.parseInt(b, 2));
}
}
方法二:
class Solution {
public String addBinary(String a, String b) {
StringBuilder ans = new StringBuilder();
int ca = 0; //进1标记位
//从后往前相加。只要有一个字符串没遍历完,就继续循环。遍历完的那个补零
for(int i = a.length() - 1, j = b.length() - 1;i >= 0 || j >= 0; i--, j--) {
int sum = ca;
//a.charAt(i) - '0'表示将数字字符转换为int。
//i >= 0 ? a.charAt(i) - '0' : 0表示如果没遍历完取其整数,遍历完补零
sum += i >= 0 ? a.charAt(i) - '0' : 0;
sum += j >= 0 ? b.charAt(j) - '0' : 0;
//如果和为2,则结果集加0,标记位置1
ans.append(sum % 2);
ca = sum / 2;
}
ans.append(ca == 1 ? ca : "");
//结果集添加的顺序从低位到高位。所以最后的结果要反转
return ans.reverse().toString();
}
}
8、假设你正在爬楼梯。需要 n 阶你才能到达楼顶。
每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?
注意:给定 n 是一个正整数。
示例 1:
输入: 2
输出: 2
解释: 有两种方法可以爬到楼顶。
- 1 阶 + 1 阶
- 2 阶
假设i阶有dp[i]种爬法。由于每次你可以爬 1 或 2 个台阶,所以dp[i]只和dp[i - 1] + dp[i - 2]有关,跟dp[i - 3]无关,因为从dp[i - 3]只能爬1或者2阶,dp[i - 1] + dp[i - 2]都包含了。而[i - 1]爬1阶后,种数还是dp[i - 1]。 [i - 2]爬两阶,种数还是dp[i - 2],[i - 2]不能爬1阶,因为会跟dp[i - 1]重复。所以dp[i - 1] + dp[i - 2]包含了所有到达i阶的可能,且没有重复。
所以状态转移方程为 dp[i] = dp[i - 1] + dp[i - 2]; 通过前面的dp[1],dp[2]不断计算可得到dp[n]
class Solution {
public int climbStairs(int n) {
//声明一个数组记录所有答案
int[] dp = new int[n + 1];
dp[0] = 1;
dp[1] = 1;
for(int i = 2; i <= n; i++) {
dp[i] = dp[i - 1] + dp[i - 2];
}
return dp[n];
}
}
9、给定一个排序链表,删除所有重复的元素,使得每个元素只出现一次。
输入: 1->1->1->2
输出: 1->2
public ListNode deleteDuplicates(ListNode head) {
//head不用动,只需要一直移动它的指针即可
ListNode current = head;
while (current != null && current.next != null) {
if (current.next.val == current.val) {
//给current.next重新赋值;注意在这里不能使用current = current.next,因为可能有多个连续相等的
current.next = current.next.next;
} else {
current = current.next;
}
}
return head;
}
10、二叉树的最大深度
方法一:递归
class Solution {
public int maxDepth(TreeNode root) {
if (root == null) return 0;
int x = maxDepth(root.left);
int y = maxDepth(root.right);
return Math.max(x,y) +1;
}
}
方法二:迭代:一层一层的来遍历二叉树,记录一下遍历的层数就是二叉树的深度
class Solution {
public int maxDepth(TreeNode root) {
if (root == null) {
return 0;
}
Queue<TreeNode> queue = new LinkedList<TreeNode>();
queue.offer(root);
int ans = 0;
while (!queue.isEmpty()) {
int size = queue.size();
while (size > 0) {
TreeNode node = queue.poll();
if (node.left != null) {
queue.offer(node.left);
}
if (node.right != null) {
queue.offer(node.right);
}
size--;
}
ans++;
}
return ans;
}
}
11、打家劫舍
你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。
给定一个代表每个房屋存放金额的非负整数数组,计算你在不触动警报装置的情况下,能够偷窃到的最高金额。
输入: [1,2,3,1]
输出: 4
解释: 偷窃 1 号房屋 (金额 = 1) ,然后偷窃 3 号房屋 (金额 = 3)。偷窃到的最高金额 = 1 + 3 = 4 。
设f(x)为打劫前x家房子所能得到的最大的资金,很容易想到动态规划的边界条件,即:
f(1)=nums[1]
f(2)=max(nums[1],nums[2])
然后是最关键的动态转移方程,如果要打劫第n家,就必然不能打劫第n-1家,所以打劫第n家得到的钱一共是第n家的钱加上前n-2家获得的最多的钱,即:f(n-2)+nums(n),如果不打劫第n家,获得的最大收益就是f(n-1),两者我们要去较大的那个,所以动态转移方程是:
f(n)=max(nums[n]+f(n-2),f(n-1))
public int rob(int[] nums) {
if (nums.length == 0) {
return 0;
} else if (nums.length == 1) {
return nums[0];
}
int[] ans = new int[nums.length];
ans[0] = nums[0];
ans[1] = Math.max(nums[0], nums[1]);
for (int i = 2; i < nums.length; i++) {
ans[i] = Math.max(nums[i] + ans[i - 2], ans[i - 1]);
}
return ans[ans.length - 1];
}
12、对称二叉树
给定一个二叉树,检查它是否是镜像对称的。
例如,二叉树 [1,2,2,3,4,4,3] 是对称的。
1
/ \
2 2
/ \ / \
3 4 4 3
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
class Solution {
public boolean isSymmetric(TreeNode root) {
if(root==null) {
return true;
}
//一棵树是否对称,可以转换为这棵树的左右子树是否对称
//调用递归函数,比较左子树和右子树是否对称
return dfs(root.left,root.right);
}
boolean dfs(TreeNode left, TreeNode right) {
//递归的终止条件是两个节点都为空
//或者两个节点中有一个为空
//或者两个节点的值不相等
if(left==null && right==null) {
return true;
}
if(left==null || right==null) {
return false;
}
if(left.val!=right.val) {
return false;
}
//再递归的比较 左节点的左孩子 和 右节点的右孩子是否对称
//以及比较 左节点的右孩子 和 右节点的左孩子是否对称
return dfs(left.left,right.right) && dfs(left.right,right.left);
}
}
13、多数元素
给定一个大小为 n 的数组,找到其中的多数元素。多数元素是指在数组中出现次数大于 ⌊ n/2 ⌋ 的元素。
你可以假设数组是非空的,并且给定的数组总是存在多数元素。
输入: [3,2,3]
输出: 3
分析:根据题目,多数元素其实只有一个
//暴力法:双重循环
class Solution {
public int majorityElement(int[] nums) {
int majorityCount = nums.length/2;
for (int num : nums) {
int count = 0;
for (int elem : nums) {
if (elem == num) {
count += 1;
}
}
if (count > majorityCount) {
return num;
}
}
return -1;
}
}
//hashmap解法:把元素和元素出现的次数放在hashmap里面
class Solution {
public int majorityElement(int[] nums) {
int majority = nums.length / 2;
Map<Integer, Integer> numCount = new HashMap<>();
for (int num : nums) {
int count = numCount.getOrDefault(num, 0) + 1;
if (count > majority) return num;
numCount.put(num, count);
}
return -1;
}
}
14、验证回文串
给定一个字符串,验证它是否是回文串,只考虑字母和数字字符,可以忽略字母的大小写。
输入: “A man, a plan, a canal: Panama”
输出: true
public static boolean isPalindrome(String str){
if(str.equals(""))
return true;
str = str.toLowerCase();//将字符串的所有大写字母转小写
int start = 0, end = str.length() - 1;
//从字符两端分别逐个对比字符,不同则直接返回false
while (start < end){
//过滤掉非字母和数字字符
while (!(str.charAt(start) >= 'a' && str.charAt(start) <= 'z' || str.charAt(start) >= '0' && str.charAt(start) <= '9'))
start++;
//过滤掉非字母和数字字符
while (!(str.charAt(end) >= 'a' && str.charAt(end) <= 'z' || str.charAt(end) >= '0' && str.charAt(end) <= '9'))
end--;
//若字符不同,则直接返回false
if(str.charAt(start) != str.charAt(end))
return false;
start++;
end--;
}
return true;
}
15、杨辉三角
给定一个非负整数 numRows,生成杨辉三角的前 numRows 行。在杨辉三角中,每个数是它左上方和右上方的数的和。
输入: 5
输出:
[
[1],
[1,1],
[1,2,1],
[1,3,3,1],
[1,4,6,4,1]
]
class Solution {
public List<List<Integer>> generate(int numRows) {
List<List<Integer>> triangle = new ArrayList<List<Integer>>();
if (numRows == 0) {
return triangle;
}
triangle.add(new ArrayList<>());
triangle.get(0).add(1); //第一个list为[1]
for (int rowNum = 1; rowNum < numRows; rowNum++) {
List<Integer> row = new ArrayList<>();
List<Integer> prevRow = triangle.get(rowNum-1); //获取前一行的list
// The first row element is always 1.
row.add(1);
// 每个数组的第j个元素=上个数组的第j-1个元素+第j个元素
for (int j = 1; j < rowNum; j++) {
row.add(prevRow.get(j-1) + prevRow.get(j)); //这里其实就是动态规划
}
// The last row element is always 1.
row.add(1);
triangle.add(row);
}
return triangle;
}
}
16、回文链表
请判断一个链表是否为回文链表。
输入: 1->2->2->1
输出: true
方法一 //遍历链表,将值按顺序放在list里面,然后反转这个list,比较一下即可
def isPalindrome(self, head: ListNode) -> bool:
vals = []
current_node = head
while current_node is not None:
vals.append(current_node.val)
current_node = current_node.next
return vals == vals[::-1]
方法二:判断一个字符串是否是回文字符串,这个比较简单,可以使用两个指针,一个最左边一个最右边,两个指针同时往中间靠,判断所指的字符是否相等。
但这题判断的是链表,因为这里是单向链表,只能从前往后访问,不能从后往前访问,所以使用判断字符串的那种方式是行不通的。
使用栈解决
我们知道栈是先进后出的一种数据结构,这里还可以使用栈先把链表的节点全部存放到栈中,然后再一个个出栈,这样就相当于链表从后往前访问了
public boolean isPalindrome(ListNode head) {
ListNode temp = head;
Stack<Integer> stack = new Stack();
//把链表节点的值存放到栈中
while (temp != null) {
stack.push(temp.val);
temp = temp.next;
}
//然后再出栈
while (head != null) {
if (head.val != stack.pop()) {
return false;
}
head = head.next;
}
return true;
}
17、x的平方根
实现 int sqrt(int x) 函数。
计算并返回 x 的平方根,其中 x 是非负整数。
由于返回类型是整数,结果只保留整数的部分,小数部分将被舍去
//二叉查找,如10的平方根一定是位于2和10/2之间的,相当于在有序数组里面找到目标值
class Solution:
def mySqrt(self, x):
if x < 2:
return x
left, right = 2, x // 2
while left <= right:
pivot = ( left + right + 1 ) // 2 //中间值
num = pivot * pivot
if num > x:
right = pivot -1
elif num < x:
left = pivot + 1
else:
return pivot
return right
18、给出一个 32 位的有符号整数,你需要将这个整数中每位上的数字进行反转。
假设我们的环境只能存储得下 32 位的有符号整数,则其数值范围为 [−231, 231 − 1]。请根据这个假设,如果反转后整数溢出那么就返回 0。
//反转整数,可以先把整数转换为字符串再反转。但这样不好,可以通过取余取整
class Solution {
public int reverse(int x) {
int res = 0;
while (x!=0) {
//判断是否 大于 最大32位整数
if (res>214748364 || (res==214748364 && x % 10>7)) {
return 0;
}
//判断是否 小于 最小32位整数
if (res<-214748364 || (res==-214748364 && x % 10<-8)) {
return 0;
}
res = res*10 + x % 10;
x = x/10;
}
return res;
}
}
19、有效的括号
给定一个只包括 ‘(’,’)’,’{’,’}’,’[’,’]’ 的字符串,判断字符串是否有效。
输入: “()”
输出: true
输入: “()[]{}”
输出: true
利用栈!!!!
class Solution {
public boolean isValid(String s) {
if (s.length() % 2 != 0) {
return false;
}
Map<Character, Character> map = new HashMap<>();
map.put('(', ')');
map.put('{', '}');
map.put('[', ']');
Stack<Character> stack = new Stack<>();
for (int i = 0; i < s.length(); i++) {
char c = s.charAt(i);
//如果是左边的括号,直接入栈
if ((c == '(' || c == '[' || c == '{')) {
stack.push(c);
continue;
}
//如果是右边括号,将栈顶元素取出进行匹配,配对成功则继续。如果匹配不成功,则返回false
if (!stack.empty() && c == map.get(stack.pop())) {
continue;
}
return false;
}
return stack.empty();
}
}
20、给定两个排序后的数组 A 和 B,其中 A 的末端有足够的缓冲空间容纳 B。 编写一个方法,将 B 合并入 A 并排序。
初始化 A 和 B 的元素数量分别为 m 和 n。
示例:
输入:
A = [1,2,3,0,0,0], m = 3
B = [2,5,6], n = 3
输出: [1,2,2,3,5,6]
方法一:将b数组的数据放到a末尾。然后调用数组的sort方法
class Solution {
public void merge(int[] A, int m, int[] B, int n) {
for(int i = 0;i<n;i++){
A[m+i] = B[i];
}
Arrays.sort(A);
}
}
方法二:
class Solution {
public void merge(int[] A, int m, int[] B, int n) {
//三指针:从A的后面开始赋值,因为如果从前面开始赋值,需要移动后面的元素
int len = m + n -1;
int j = m - 1;
int k = n - 1;
for (int i=len; i>= 0; i--) {
if (j > -1 && k > -1) {
if (j > -1 && k > -1 && A[j] > B[k]) {
A[i] = A[j];
j--;
} else {
A[i] = B[k];
k--;
}
}
}
//A循环完了,将B剩下的值赋给A
while (k >= 0) {
A[k] = B[k] ;
k--;
}
}
}
21、移除元素
给你一个数组 nums 和一个值 val,你需要 原地 移除所有数值等于 val 的元素,并返回移除后数组的新长度。
不要使用额外的数组空间,你必须仅使用 O(1) 额外空间并 原地 修改输入数组。
//双指针法
class Solution {
public int removeElement(int[] nums, int val) {
int i = 0;
for (int j=0; j<nums.length; j++) {
if (nums[j] != val) {
nums[i] = nums[j];
i++;
}
}
return i;
}
}
22、给你一个字符串 s,由若干单词组成,单词之间用空格隔开。返回字符串中最后一个单词的长度。如果不存在最后一个单词,请返回 0 。
方法一:用Java api:先用trim方法去掉首尾字符串,再用split方法得到最后一个单词。但是这样需要遍历所有字符串,复杂度较高。
方法二:从尾遍历,及时return,时间复杂度最低
class Solution {
public int lengthOfLastWord(String s) {
if (s.length() == 0) return 0;
int count = 0;
for (int i = s.length() - 1; i >= 0; i--) {
//如果当前字符是空格,且前面已经扫描过字母字符
if (s.charAt(i) == ' ' && count != 0) return count;
//如果是字符串尾部空格
else if (s.charAt(i) == ' ') count = 0;
else count ++;
}
return count;
}
}
23、合并两个有序数组
第一种情况:将合并的数组放到第三个数组里面
import java.util.Arrays;
public class Test {
public static void main(String[] args) {
int[] a = { 100, 89, 88, 67, 65, 34 };
int[] b = { 120, 110, 103, 79 };
int a_len = a.length;
int b_len = b.length;
int[] result = new int[a_len + b_len];
// i:用于标示a数组 j:用来标示b数组 k:用来标示传入的数组
int i = 0;
int j = 0;
int k = 0;
while (i < a_len && j < b_len) {
if (a[i] >= b[i])
result[k++] = a[i++];
else
result[k++] = b[j++];
}
// 后面连个while循环是用来保证两个数组比较完之后剩下的一个数组里的元素能顺利传入
while (i < a_len) {
result[k++] = a[i++];
}
while (j < b_len) {
result[k++] = b[j++];
}
System.out.println(Arrays.toString(a));
System.out.println(Arrays.toString(b));
System.out.println(Arrays.toString(result));
}
}
第二种情况:将合并的数组放到第一个数组上
注意题目给出的数组的方式:nums1 中本身有效的数字是前 m 位,nums1 的长度是 m + n,因此正好可以放下 nums1[0:m] + nums2[0:n]。
合并两个有序数组,我们第一反应肯定是想到了归并排序。归并排序是把两个有序的数组合并、放到另外一个数组中。所以空间复杂度是 O(M + N)的。
由于本题给出的 nums1 是能够保证其长度能够放得下 m + n 个元素的,所以可以直接把合并的结果放到 nums1 中。
思路一:如果两个数组从开头向结尾(数字从小到大)进行比较,那么每次把比较之后的数字放置到 nums1 中的前面,则需要把 nums1 中第 k 个位置后面的元素向后移动。移动次数比较多。
思路二:如果两个数组从结尾向开头(数字从大到小)进行比较,那么每次把比较之后的数字放置到 nums1 中的后面,由于后面的数字本身就是提供出来的多余的位置,都是 0,因此不需要对 nums1 进行移动。
显然思路二更好。
从后向前进行比较
确定了主要的思路之后,实现起来其实很简单。
当 m > 0 并且 n > 0 时,从后向前比较 num1[m - 1] 和 nums2[n - 1]:
如果是 nums1[m - 1] 大,则把 nums1[m - 1]]放到 num1的第 m + n - 1位置,并让 m -= 1。
当上面的遍历条件结束的时候,此时 m 和 n 至少有一个为 0。
当 m == 0 时,说明 num1 的数字恰好用完了,此时 nums2 可能还剩元素,需要复制到 nums1 的头部;
当 n == 0 时,说明 num2 的数字恰好用完了,此时 nums1 可能还剩元素,由于剩余的这些元素一定是 nums1 和 nums2 中最小的元素,所以不用动,直接留在原地就行。
class Solution {
public void merge(int[] nums1, int m, int[] nums2, int n) {
int k = m + n - 1;
while (m > 0 && n > 0) {
if (nums1[m - 1] > nums2[n - 1]) {
nums1[k] = nums1[m - 1];
m --;
} else {
nums1[k] = nums2[n - 1];
n --;
}
k --;
}
for (int i = 0; i < n; ++i) {
nums1[i] = nums2[i];
}
}
}
24、给你两棵二叉树的根节点 p 和 q ,编写一个函数来检验这两棵树是否相同。
方法一:深度优先搜索
class Solution {
public boolean isSameTree(TreeNode p, TreeNode q) {
if (p == null && q == null) {
return true;
} else if (p == null || q == null) {
return false;
} else if (p.val != q.val) {
return false;
} else {
return isSameTree(p.left, q.left) && isSameTree(p.right, q.right);
}
}
}
方法二:广度优先搜索
class Solution {
public boolean isSameTree(TreeNode p, TreeNode q) {
if (p == null && q == null) {
return true;
} else if (p == null || q == null) {
return false;
}
Queue<TreeNode> queue1 = new LinkedList<TreeNode>();
Queue<TreeNode> queue2 = new LinkedList<TreeNode>();
queue1.offer(p);
queue2.offer(q);
while (!queue1.isEmpty() && !queue2.isEmpty()) {
TreeNode node1 = queue1.poll();
TreeNode node2 = queue2.poll();
if (node1.val != node2.val) {
return false;
}
TreeNode left1 = node1.left, right1 = node1.right, left2 = node2.left, right2 = node2.right;
if (left1 == null ^ left2 == null) {
return false;
}
if (right1 == null ^ right2 == null) {
return false;
}
if (left1 != null) {
queue1.offer(left1);
}
if (right1 != null) {
queue1.offer(right1);
}
if (left2 != null) {
queue2.offer(left2);
}
if (right2 != null) {
queue2.offer(right2);
}
}
return queue1.isEmpty() && queue2.isEmpty();
}
}
25、给你一个整数数组 nums ,其中元素已经按 升序 排列,请你将其转换为一棵 高度平衡 二叉搜索树。
高度平衡 二叉树是一棵满足「每个节点的左右两个子树的高度差的绝对值不超过 1 」的二叉树。
二叉搜索树的中序遍历是升序序列,题目给定的数组是按照升序排序的有序数组,因此可以确保数组是二叉搜索树的中序遍历序列。
方法一:中序遍历,总是选择中间位置左边的数字作为根节点
class Solution {
public TreeNode sortedArrayToBST(int[] nums) {
return helper(nums, 0, nums.length - 1);
}
public TreeNode helper(int[] nums, int left, int right) {
if (left > right) {
return null;
}
// 总是选择中间位置左边的数字作为根节点
int mid = (left + right) / 2;
TreeNode root = new TreeNode(nums[mid]);
root.left = helper(nums, left, mid - 1);
root.right = helper(nums, mid + 1, right);
return root;
}
}
26、给定一个二叉树,判断它是否是高度平衡的二叉树。
本题中,一棵高度平衡二叉树定义为:
一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过 1 。
从顶至底(暴力法)
此方法容易想到,但会产生大量重复计算,时间复杂度较高。
思路是构造一个获取当前节点最大深度的方法 depth(root) ,通过比较此子树的左右子树的最大高度差abs(depth(root.left) - depth(root.right)),来判断此子树是否是二叉平衡树。若树的所有子树都平衡时,此树才平衡
class Solution {
public boolean isBalanced(TreeNode root) {
if (root == null) return true;
return Math.abs(depth(root.left) - depth(root.right)) <= 1 && isBalanced(root.left) && isBalanced(root.right);
}
private int depth(TreeNode root) {
if (root == null) return 0;
return Math.max(depth(root.left), depth(root.right)) + 1;
}
}
27、加一
给定一个由整数组成的非空数组所表示的非负整数,在该数的基础上加一。
最高位数字存放在数组的首位, 数组中每个元素只存储单个数字。
你可以假设除了整数 0 之外,这个整数不会以零开头。
输入: [1,2,3]
输出: [1,2,4]
解释: 输入数组表示数字 123。
这道题需要整理出来有哪几种情况,在进行处理会更舒服:
末位无进位,则末位加一即可,因为末位无进位,前面也不可能产生进位,比如 45 => 46
末位有进位,在中间位置进位停止,则需要找到进位的典型标志,即为当前位 %10 后为 0,则前一位加 1,直到不为 0 为止,比如 499 => 500
末位有进位,并且一直进位到最前方导致结果多出一位,对于这种情况,需要在第 2 种情况遍历结束的基础上,进行单独处理,比如 999 => 1000
class Solution {
public int[] plusOne(int[] digits) {
int len = digits.length;
for(int i = len - 1; i >= 0; i--) {
digits[i]++;
digits[i] %= 10;
if(digits[i]!=0)
return digits;
}
digits = new int[len + 1];
digits[0] = 1;
return digits;
}
}
29、买卖股票最大利润:只能买卖一次
public class MaxPrice {
//暴力法:两层for循环
public static int maxProfit1(int[] prices) {
int maxPrice = 0;
for (int i=0; i<prices.length; i++) {
for (int j=i+1; j<prices.length; j++) {
maxPrice = Math.max(maxPrice, prices[j]-prices[i]);
}
}
return maxPrice;
}
//方法二:只遍历一次,每次记录扫描过的元素的最小值和当前最大利润,时间复杂度是O(n)
public int maxProfit2(int[] prices) {
if(prices == null || prices.length < 1){
return 0;
}
int min = prices[0];
int maxprofit = 0;
for(int i=0;i<prices.length;i++){
if(prices[i] < min){ //如果当前元素比最小值小,则更新最小值
min = prices[i];
}else{ //如果当前元素比最小值大,且如果当前元素-最小值大于最大利润,则更新最大利润
if(maxprofit < prices[i]-min){
maxprofit = prices[i]-min;
}
}
}
return maxprofit;
}
}
29.1、给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。
设计一个算法来计算你所能获取的最大利润。你可以尽可能地完成更多的交易(多次买卖一支股票)。
注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。
假设股票是连续上涨的,价格分别为p1,p2,..pn,那么最大收益是第一天买最后一天卖,即pn-p1,等价于每天都买卖,即pn-p1=(p2-p1)+(p3-p2)+..(pn-pn-1)。
当中间有下跌的,比如p2到p3是跌的,那么p2到p3不交易即可。即最大利润等于所有上涨交易日都买卖即可
class Solution {
public int maxProfit(int[] prices) {
int profit = 0;
for (int i = 1; i < prices.length; i++) {
int tmp = prices[i] - prices[i - 1];
if (tmp > 0) profit += tmp;
}
return profit;
}
}
30、只出现一次的数字
给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。
输入: [4,1,2,1,2]
输出: 4
说明:
你的算法应该具有线性时间复杂度。 你可以不使用额外空间来实现吗?
要求线性时间复杂度即O(n),不能用两层for循环,那么用hash可解决。但是又要求不使用额外空间,即空间复杂度有 要求,那么不能用hash。
既满足时间复杂度又满足空间复杂度,就要提到位运算中的异或运算:
x与0异或还是x。x和x异或为0。
结合律 (a^b)^c=a^(a^c)
class Solution {
public int singleNumber(int[] nums) {
int ans = 0;
for(int num: nums) {
ans ^= num;
}
return ans;
}
}
31、环形链表
给定一个链表,判断链表中是否有环。
方法一:哈希表
使用哈希表来存储所有已经访问过的节点。每次我们到达一个节点,如果该节点已经存在于哈希表中,则说明该链表是环形链表,否则就将该节点加入哈希表中。重复这一过程,直到我们遍历完整个链表即可。
时间复杂度:O(N),其中 N 是链表中的节点数。最坏情况下我们需要遍历每个节点一次。
空间复杂度:O(N),其中 N是链表中的节点数。主要为哈希表的开销,最坏情况下我们需要将每个节点插入到哈希表中一次。
public class Solution {
public boolean hasCycle(ListNode head) {
Set<ListNode> seen = new HashSet<ListNode>();
while (head != null) {
if (!seen.add(head)) {
return true;
}
head = head.next;
}
return false;
}
}
方法二:快慢指针
定义两个指针,一快一满。慢指针每次只移动一步,而快指针每次移动两步。初始时,慢指针在位置 head,而快指针在位置 head.next。这样一来,如果在移动的过程中,快指针反过来追上慢指针,就说明该链表为环形链表。否则快指针将到达链表尾部,该链表不为环形链表。
public class Solution {
public boolean hasCycle(ListNode head) {
if (head == null || head.next == null) {
return false;
}
ListNode slow = head;
ListNode fast = head.next;
while (slow != fast) {
if (fast == null || fast.next == null) {
return false;
}
slow = slow.next;
fast = fast.next.next;
}
return true;
}
}
31.1、环形链表2
给定一个链表,返回链表开始入环的第一个节点。注意上面的31只是说明是否是环形链表,但是快慢指针相交的点可能是环中的任意一点。并不是入环第一个节点。
方法一:哈希表
public class Solution {
public ListNode detectCycle(ListNode head) {
ListNode pos = head;
Set<ListNode> visited = new HashSet<ListNode>();
while (pos != null) {
if (visited.contains(pos)) {
return pos;
} else {
visited.add(pos);
}
pos = pos.next;
}
return null;
}
}
31.2 相交链表
// 1.定义一个集合。
// 2.其中一个链表的节点加入集合。(链表的当前节点代表链表的当前节点及之后的节点)
// 3.判断另外一个链表的节点是否有存在在集合中的,集合中有,则为相交,要是集合中没有,则为不相交。
//public class IntersectionList {
// public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
// Set<ListNode> set = new HashSet<ListNode> ();
// while(headA!=null){
// set.add(headA);
// headA=headA.next;
// }
// while(headB!=null)
// {
// if(set.contains(headB))
// return headB;
// headB=headB.next;
// }
// return null;
// }
//}
32、最小栈
设计一个支持 push,pop,top 操作,并能在常数时间内检索到最小元素的栈。
push(x) – 将元素 x 推入栈中。
pop() – 删除栈顶的元素。
top() – 获取栈顶元素。但不删除
getMin() – 检索栈中的最小元素。
使用一个辅助栈,与元素栈同步插入与删除,用于存储与每个元素对应的最小值。当一个元素要入栈时,我们取当前辅助栈的栈顶存储的最小值,与当前元素比较得出最小值,将这个最小值插入辅助栈中;当一个元素要出栈时,我们把辅助栈的栈顶元素也一并弹出; 在任意一个时刻,栈内元素的最小值就存储在辅助栈的栈顶元素中。
class MinStack {
Deque<Integer> xStack;
Deque<Integer> minStack;
public MinStack() {
xStack = new LinkedList<Integer>();
minStack = new LinkedList<Integer>();
minStack.push(Integer.MAX_VALUE);
}
//push每次操作都往最小栈里面存对应的最小值
//所以pop操作时,需要弹出主栈元素及其对应的最小栈中的最小值。如果不弹最小栈元素,则主栈和最小栈的元素将失去对应关系
public void push(int x) {
xStack.push(x);
minStack.push(Math.min(minStack.peek(), x));
}
public void pop() {
xStack.pop();
minStack.pop();
}
public int top() {
return xStack.peek();
}
public int getMin() {
return minStack.peek();
}
}
34、反转二叉树
原二叉树:
4
/ \
2 7
/ \ / \
1 3 6 9
反转后的二叉树:
4
/ \
7 2
/ \ / \
9 6 3 1
class Solution {
public TreeNode invertTree(TreeNode root) {
if (root == null) {
return null;
}
TreeNode left = invertTree(root.left);
TreeNode right = invertTree(root.right);
root.left = right;
root.right = left;
return root;
}
}
35、反转一个单链表。
示例:
输入: 1->2->3->4->5->NULL
输出: 5->4->3->2->1->NULL
class Solution {
public ListNode reverseList(ListNode head) {
//申请节点,pre和 cur,pre指向null
ListNode pre = null;
ListNode cur = head;
ListNode tmp = null;
while(cur!=null) {
//记录当前节点的下一个节点。因为下一步要改变当前节点的指向
tmp = cur.next;
//然后将当前节点指向pre
cur.next = pre;
//pre和cur节点都前进一位
pre = cur;
cur = tmp;
}
return pre;
}
}
36、合并二叉树
给定两个二叉树,想象当你将它们中的一个覆盖到另一个上时,两个二叉树的一些节点便会重叠。
你需要将他们合并为一个新的二叉树。合并的规则是如果两个节点重叠,那么将他们的值相加作为节点合并后的新值,否则不为 NULL 的节点将直接作为新二叉树的节点。
class Solution {
public TreeNode mergeTrees(TreeNode root1, TreeNode root2) {
if(root2 == null) return root1;
if(root1 == null) return root2;
//对当前节点【值】进行相加
root1.val += root2.val;
//对当前节点的左右节点进行递归
root1.left = mergeTrees(root1.left, root2.left);
root1.right = mergeTrees(root1.right, root2.right);
return root1;
}
}
37、二叉树的直径
给定一棵二叉树,你需要计算它的直径长度。一棵二叉树的直径长度是任意两个结点路径长度中的最大值。这条路径可能穿过也可能不穿过根结点。
可以将二叉树的直径转换为:二叉树的 每个节点的左右子树的高度和 的最大值。
root的直径 = root左子树高度 + root右子树高度(途径的边数)
root的高度 = max {root左子树高度, root右子树高度} + 1
//在求二叉树的深度的代码基础上,在递归过程中更新一下“每个节点的左右子树的高度和的最大值”即可
//因为求二叉树的最大深度的过程中,会有左右子树的最大深度,所以刚好每次递归的时候,比较每次左右子树的最大深度和即可
class Solution {
int ans;
public int diameterOfBinaryTree(TreeNode root) {
ans = 1;
depth(root);
return ans - 1;
}
public int depth(TreeNode node) {
if (node == null) {
return 0; // 访问到空节点了,返回0
}
int L = depth(node.left); // 左儿子为根的子树的深度
int R = depth(node.right); // 右儿子为根的子树的深度
ans = Math.max(ans, L+R+1); //更新左右子树最大深度和。和“求二叉树最大深度”的区别就在此
return Math.max(L, R) + 1; // 返回该节点为根的子树的深度
}
}
38、LRU cache
即最近最少使用算法,有get和put方法,当缓存满了之后,put的时候需要将最近最少使用的对象删除。
要求 O(1) 时间复杂度。
实现思路:当get的时候,如果数据存在,则将该数据移到头部,并返回值。put的时候,如果数据不存在,则将数据插入到头部,如果存在,则将数据删除,并将数据插入头部。也就是头部存放最新的数据,当缓存满了则删除尾部元素即可。
实现一:使用数组可以实现,但是数组查询和插入时间复杂度是O(n)
实现二:使用单链表可以实现,且插入(头和尾只需要一步操作)时间复杂度是O(1)。但查询get的时候复杂度无法满足O(1)。想要get达到O(1),可加上Hashmap结构。但是根据Hashmap查到目标值后,我们需要将目标值移到链表头部(即把目标值前面的节点指针指向目标值下一个,然后将目标值的指针指向头节点)。如果是单向链表,找到目标值后是无法在O(1)复杂度情况下找到其前一个节点的。所以这里需要双向链表。
综上,最终使用Hashmap+双向链表结构实现LRU。
在JDK里面,LinkedHashMap就是一个简单LRU数据结构。
public class LRUCache {
//每个双向链表的节点都有一个K值,一个V值,以及前向指针和后向指针
class DLinkedNode {
int key;
int value;
DLinkedNode prev;
DLinkedNode next;
public DLinkedNode() {}
public DLinkedNode(int _key, int _value) {key = _key; value = _value;}
}
private Map<Integer, DLinkedNode> cache = new HashMap<Integer, DLinkedNode>();
private int size; //统计put方法中放了几个元素
private int capacity;
private DLinkedNode head, tail;
//初始化LRU缓存,构建一个只有头尾节点的双指针
public LRUCache(int capacity) {
this.size = 0;
this.capacity = capacity;
// 使用伪头部和伪尾部节点
head = new DLinkedNode();
tail = new DLinkedNode();
head.next = tail;
tail.prev = head;
}
public int get(int key) {
DLinkedNode node = cache.get(key);
if (node == null) {
return -1;
}
// 如果 key 存在,先将节点移到头部,再取节点值(节点位置移动不影响value)
moveToHead(node);
return node.value;
}
public void put(int key, int value) {
DLinkedNode node = cache.get(key);
if (node == null) {
// 如果 key 不存在,创建一个新的节点
DLinkedNode newNode = new DLinkedNode(key, value);
// 添加进哈希表
cache.put(key, newNode);
// 添加至双向链表的头部
addToHead(newNode);
++size;
if (size > capacity) {
// 如果超出容量,删除双向链表的尾部节点
DLinkedNode tail = removeTail();
// 删除哈希表中对应的项
cache.remove(tail.key);
--size;
}
}
else {
// 如果 key 存在,先修改 value,并移到头部
node.value = value;
moveToHead(node);
}
}
private void addToHead(DLinkedNode node) {
node.prev = head;
node.next = head.next;
head.next.prev = node;
head.next = node;
}
private void removeNode(DLinkedNode node) {
node.prev.next = node.next;
node.next.prev = node.prev;
}
private void moveToHead(DLinkedNode node) {
removeNode(node);
addToHead(node);
}
private DLinkedNode removeTail() {
DLinkedNode res = tail.prev;
removeNode(res);
return res;
}
}
39、无重复字符的最长子串的长度
法一:暴力解法,两个for循环
法二:滑动窗口
暴力解法时间复杂度较高,会达到 O(n2),故而采取滑动窗口的方法降低时间复杂度
定义一个 map 数据结构存储 (k, v),其中 key 值为字符,value 值为字符位置 +1,加 1 表示从字符位置后一个才开始不重复
我们定义不重复子串的开始位置为 start,结束位置为 end
随着 end 不断遍历向后,会遇到与 [start, end] 区间内字符相同的情况,此时将字符作为 key 值,获取其 value 值,并更新 start,此时 [start, end] 区间内不存在重复字符
无论是否更新 start,都会更新其 map 数据结构和结果 ans。
时间复杂度:O(n)
class Solution {
public int lengthOfLongestSubstring(String s) {
int n = s.length(), ans = 0;
Map<Character, Integer> map = new HashMap<>();
//每次移动end指针,如果没有重复start不用移动。如果有重复,如abcb,将start设为重复的值的下标,即这里是b的下标1
for (int end = 0, start = 0; end < n; end++) {
char alpha = s.charAt(end);
if (map.containsKey(alpha)) {
//比如tabba, 遍历到第二个b时,会将左指针start设为2,;然后遍历到最后一个a时,此时map.get("a")=1,如果不将map.get("a")和start值比大小,那么又将start倒退回去置为1,那么结果错误。而如果是tabca,下面的代码写成start=map.get(alpha)是没问题的,因为遇到最后一个a之前,左指针都没有动过,也就不会回退
start = Math.max(map.get(alpha), start);
}
ans = Math.max(ans, end - start + 1);
map.put(s.charAt(end), end + 1);
}
return ans;
}
}
40、最长回文子串
输入:s = “babad”
输出:“bab”
解释:“aba” 同样是符合题意的答案。
暴力求解,两层for循环列举所有的子串,判断是否为回文串,保存最长的回文串。
public boolean isPalindromic(String s) {
int len = s.length();
for (int i = 0; i < len / 2; i++) {
if (s.charAt(i) != s.charAt(len - i - 1)) {
return false;
}
}
return true;
}
// 暴力解法
public String longestPalindrome(String s) {
String ans = "";
int max = 0;
int len = s.length();
for (int i = 0; i < len; i++)
for (int j = i + 1; j <= len; j++) {
String test = s.substring(i, j);
if (isPalindromic(test) && test.length() > max) {
ans = s.substring(i, j);
max = Math.max(max, ans.length());
}
}
return ans;
}
41、三数之和
给你一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?请你找出所有和为 0 且不重复的三元组。
首先对数组进行升序排序,排序后固定一个数 nums[i],再使用左右指针指向 nums[i]后面的两端,数字分别为 nums[L]和 nums[R],计算三个数的和 sum判断是否满足为 0,满足则添加进结果集;
如果 nums[i]大于 0,则三数之和必然无法等于 0,结束循环;
如果 nums[i] == nums[i−1],则说明该数字重复,会导致结果重复,所以应该跳过;
当 sum == 0 时,nums[L] == nums[L+1] 则会导致结果重复,应该跳过,L++;(数据是排好序的,要重复就是相邻重复)
当 sum == 0时,nums[R] == nums[R−1] 则会导致结果重复,应该跳过,R−−;
class Solution {
public static List<List<Integer>> threeSum(int[] nums) {
List<List<Integer>> ans = new ArrayList();
int len = nums.length;
if(nums == null || len < 3) return ans;
Arrays.sort(nums); // 升序排序
//固定三个指针中最左边的指针
for (int i = 0; i < len ; i++) {
if(nums[i] > 0) break; // 如果当前数字大于0,则三数之和一定大于0,所以结束循环
if(i > 0 && nums[i] == nums[i-1]) continue; // 去重
//再使用左右指针指向 nums[i]后面的两端
int L = i+1;
int R = len-1;
while(L < R){
int sum = nums[i] + nums[L] + nums[R];
if(sum == 0){
ans.add(Arrays.asList(nums[i],nums[L],nums[R]));
while (L<R && nums[L] == nums[L+1]) L++; // 去重
while (L<R && nums[R] == nums[R-1]) R--; // 去重
L++;
R--;
}
else if (sum < 0) L++;
else if (sum > 0) R--;
}
}
return ans;
}
}
42、二叉树的遍历
前序:根左右
中序:左根右,先遍历左子树,左子树遍历的时候也要根据左根右遍历。而且要遍历完左子树才能轮到根节点
后序:左右根
层次:从上到下,从左往右
前中后其实是根据根节点的位置定的,根节点排最后就是左右根
递归解法:
前序
public static void preOrderRecur(TreeNode head) {
if (head == null) {
return;
}
System.out.print(head.value + " ");
preOrderRecur(head.left);
preOrderRecur(head.right);
}
中序
public static void preOrderRecur(TreeNode head) {
if (head == null) {
return;
}
preOrderRecur(head.left);
System.out.print(head.value + " ");
preOrderRecur(head.right);
}
后序
public static void postOrderRecur(TreeNode head) {
if (head == null) {
return;
}
postOrderRecur(head.left);
postOrderRecur(head.right);
System.out.print(head.value + " ");
}
迭代解法
本质上是在模拟递归,因为在递归的过程中使用了系统栈,所以在迭代的解法中常用Stack来模拟系统栈。
前序
public static void preOrderIteration(TreeNode head) {
if (head == null) {
return;
}
Stack<TreeNode> stack = new Stack<>();
stack.push(head);
while (!stack.isEmpty()) {
//根节点要先遍历,所以根出栈,打印
TreeNode node = stack.pop();
System.out.print(node.value + " ");
//先加右子树再加左子树,这样左子树才能先弹出
if (node.right != null) {
stack.push(node.right);
}
if (node.left != null) {
stack.push(node.left);
}
}
}
后序
前序遍历的过程 是 中左右。
将其转化成 中右左。也就是压栈的过程中优先压入左子树,在压入右子树。
然后将这个结果返回来,这里是利用栈的先进后出倒序打印。
public static void postOrderIteration(TreeNode head) {
if (head == null) {
return;
}
Stack<TreeNode> stack1 = new Stack<>();
Stack<TreeNode> stack2 = new Stack<>();
stack1.push(head);
while (!stack1.isEmpty()) {
TreeNode node = stack1.pop();
stack2.push(node);
if (node.left != null) {
stack1.push(node.left);
}
if (node.right != null) {
stack1.push(node.right);
}
}
while (!stack2.isEmpty()) {
System.out.print(stack2.pop().value + " ");
}
}
中序
class Solution {
public List<Integer> inorderTraversal(TreeNode root) {
List<Integer> res = new ArrayList<Integer>();
Deque<TreeNode> stk = new LinkedList<TreeNode>();
while (root != null || !stk.isEmpty()) {
//这个while循环的作用就是把根以及左节点都放到stack里面,且最远的左节点最后进栈。
while (root != null) {
stk.push(root);
root = root.left;
}
root = stk.pop();
res.add(root.val);
//如果弹出的节点没有右节点,那么root为null,继续弹出;如果有,那么将这个弹出节点的右节点r入栈,如果这个r没有子节点,就会弹出r
root = root.right;
}
return res;
}
}
层序
class Solution {
public List<List<Integer>> levelOrder(TreeNode root) {
List<List<Integer>> ret = new ArrayList<List<Integer>>();
if (root == null) {
return ret;
}
Queue<TreeNode> queue = new LinkedList<TreeNode>();
queue.offer(root);
while (!queue.isEmpty()) {
List<Integer> level = new ArrayList<Integer>();
int currentLevelSize = queue.size();
//一层的数据全部操作完:包括将当前层的数据全都加到level里面,把下一层的所有数据加到queue里面
while (currentLevelSize>0) {
TreeNode node = queue.poll();
level.add(node.val);
if (node.left != null) {
queue.offer(node.left);
}
if (node.right != null) {
queue.offer(node.right);
}
currentLevelSize --;
}
ret.add(level);
}
return ret;
}
}
43、查找字符串数组中的最长公共前缀。
//输入:strs = [“flower”,“flow”,“flight”]
//输出:“fl”
public class longestCommonPrefix {
public static void main(String[] args) {
String[] s = {"laojingyao","lajingyao","laojing"};
System.out.println(longestCommonPrefix(s));
}
public static String longestCommonPrefix(String[] strs) {
int minlen = strs[0].length();
//先计算长度最小的数组的长度
for (int i=1; i<strs.length; i++) {
if (strs[i].length() < minlen) {
minlen = strs[i].length();
}
}
String commstr = "";
//循环最小长度
for(int i=0; i<minlen; i++){
//两两对比同一位置字符,只要有不相等的情况就可以返回了。
//比较是不是都相等,可以通过"只要有不相等就返回"来实现
for (int j=0;j<strs.length-1; j++) {
if (strs[j].charAt(i) != strs[j+1].charAt(i)) {
return commstr;
}
}
commstr = commstr + strs[0].charAt(i);
}
return commstr;
}
}
44、移动0
给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。必须在原数组上操作,不能拷贝额外的数组。
public class Move0 {
public void moveZeroes(int[] nums) {
int index = 0; //指向非零元素
for(int i = 0; i < nums.length; i++){
if(nums[i] != 0) {
nums[index++] = nums[i];
}
}
for(int i = index; i < nums.length; i++){
nums[i] = 0;
}
}
}