二叉树题型
解法综述:二叉树的解法,基本上都是依赖遍历,再加上递归的思路来做的。那递归又分为深度优先和广度优先。深度优先算法,前序,中序,后序。广度优先,利用先进先出队列,一层一层的遍历完整颗树。
深度优先
1. 求二叉树的最大深度
class Solution {
public int maxDepth(TreeNode root) {
return deep(root, 0);
}
public int deep(TreeNode node, int deep){
if (node == null) {
return deep;
}
return Math.max(deep(node.left, deep + 1), deep(node.right, deep + 1));
}
}
2. 是否是相同的树
class Solution {
public boolean isSameTree(TreeNode p, TreeNode q) {
return isSame(p, q);
}
public boolean isSame(TreeNode p, TreeNode q){
if (p == null && q == null) {
return true;
}
if ((p == null && q != null) || (p != null && q == null)) {
return false;
}
return p.val == q.val && isSame(p.left, q.left) && isSame(p.right, q.right);
}
}
3. 求根节点到叶节点数字之和
class Solution {
public int sumNumbers(TreeNode root) {
return sum(root, 0);
}
public int sum(TreeNode node, int sum){
if (node == null) {
return 0;
}
if (node.left == null && node.right == null) {
return 10*sum + node.val;
}
return sum(node.left,node.val + sum * 10) + sum(node.right, node.val + sum * 10);
}
}
广度优先
1. 填充每个节点的下一个右侧节点指针
class Solution {
public Node connect(Node root) {
if (root == null) {
return null;
}
LinkedList<Node> list = new LinkedList<>();
list.add(root);
while(list.size()!=0){
int size = list.size();
Node pre = null;
for (int i=0; i<size; i++) {
Node cur = list.poll();
if (pre != null) {
pre.next = cur;
}
if (cur.left != null) {
list.add(cur.left);
}
if (cur.right != null) {
list.add(cur.right);
}
pre = cur;
}
}
return root;
}
}
2. 二叉树的右视图
class Solution {
public List<Integer> rightSideView(TreeNode root) {
if (root == null) {
return new ArrayList<>();
}
LinkedList<TreeNode> list = new LinkedList<>();
list.add(root);
List<Integer> resList = new ArrayList<>();
while(!list.isEmpty()){
int size = list.size();
for(int i=0; i<size; i++){
TreeNode node = list.poll();
if (node.left != null) {
list.add(node.left);
}
if (node.right != null) {
list.add(node.right);
}
if(i==(size-1)){
resList.add(node.val);
}
}
}
return resList;
}
}
3. 二叉树的层平均值
class Solution {
public List<Double> averageOfLevels(TreeNode root) {
return average(root);
}
//offer -- add 加在队列的尾部
//poll -- 从队列的头部取元素
//pop -- 从队列的头部取元素
//push -- 放在队列头部
public List<Double> average(TreeNode root){
List<Double> res = new ArrayList<Double>();
LinkedList<TreeNode> list = new LinkedList<TreeNode>();
list.add(root);
while(list.size() != 0){
int size = list.size();
double sum = 0.0;
for (int i=0; i<size; i++) {
TreeNode node = list.poll();
sum = sum + node.val;
if (node.left != null) {
list.add(node.left);
}
if (node.right != null) {
list.add(node.right);
}
}
res.add(sum/size);
}
return res;
}
}
二分查找
解法综述:首先要基于一个有序数组或者是要局部有序的场景,才能比较适合实用二分查找。然后需要通过数组的左边界和右边界确定数组的中间值,再与目标值进行对比。然后不断地调整左右边界,直到左边界大于右边界。
1. 搜索插入位置
class Solution {
public int searchInsert(int[] nums, int target) {
int left = 0;
int right = nums.length -1;
int mid = 0;
int ans = nums.length;
while(left<=right){
mid = left + (right - left)/2;
if (target <= nums[mid]) {
right = mid - 1;
ans = mid;
} else {
left = mid + 1;
}
}
return ans;
}
}
2. 寻找峰值(这就是一个局部有序的场景)
class Solution {
public int findPeakElement(int[] nums) {
int n = nums.length;
int left = 0, right = n - 1, ans = -1;
while (left <= right) {
int mid = (left + right) / 2;
if (compare(nums, mid - 1, mid) < 0 && compare(nums, mid, mid + 1) > 0) {
ans = mid;
break;
}
if (compare(nums, mid, mid + 1) < 0) {
left = mid + 1;
} else {
right = mid - 1;
}
}
return ans;
}
int[] get(int[] nums, int a){
if (a < 0 || a > nums.length - 1) {
return new int[]{0,0};
}
return new int[]{1, nums[a]};
}
public int compare(int[] nums, int a, int b){
int[] ar = get(nums, a);
int[] br = get(nums, b);
if (ar[0] > br[0]) {
return 1;
} else if (ar[0] == br[0] ) {
if (ar[1] > br[1]) {
return 1;
}
else if (ar[1] == br[1]) {
return 0;
} else {
return -1;
}
} else {
return -1;
}
}
}
堆
1. 数组中k个最大元素(这个使用冒泡排序也是可以做的,复杂度k*n; 堆的复杂度 k*logn,)
class Solution {
public int findKthLargest(int[] nums, int k) {
//第一次大顶堆要完整构建
//第k-1次大顶堆,使用局部构造大顶堆的方式进行
//构建好了大顶堆要,交换,并且长度要减1
//剔除到第k次时,就是第k个最大值
int length = nums.length;
buildMaxHeap(nums, length);
swap( 0, length - 1, nums);
length = length - 1;
for(int i =0 ; i<k-1;i++){
buildSubMaxHeap(nums, 0,length);
swap( 0, length - 1, nums);
length = length - 1;
}
return nums[nums.length-k];
}
void buildMaxHeap(int[] nums, int length){
int start = length/2 - 1;
for (int i = start ; i >=0; i--) {
buildSubMaxHeap(nums, i, length);
}
}
void buildSubMaxHeap(int[] nums, int pos,int length){
int left = 2 * pos + 1;
int right = 2 * pos + 2;
int large = pos;
if (left<length && nums[left] > nums[large]) {
large = left;
}
if (right<length && nums[right] > nums[large]) {
large = right;
}
if (large != pos) {
swap(large, pos, nums);
buildSubMaxHeap(nums, large, length);
}
}
void swap(int a, int b , int[] nums){
int temp = nums[a];
nums[a] = nums[b];
nums[b] = temp;
}
}
链表
解法综述:
1. 单链表,有两个指针,一个快,一个慢。或者一个pre,一个cur
2. 双链表,有两个指针,一个快,一个慢
3. 使用递归 + map的方式来解
4. 需要使用3个指针,pre,cur,next
5. LRU缓存-涉及到的数据结构很多,最好还是使用LinkedHashMap实现
6. 只使用一个cur,但是会使用cur.next, cur.next.next, 这种方式适合处理相邻元素的问题
单链表,有两个指针,一个快,一个慢
1. 环形链表
public class Solution {
public boolean hasCycle(ListNode head) {
if (head == null || head.next == null) {
return false;
}
ListNode first = head;
ListNode second = head.next;
while (second != null) {
if (first == second) {
return true;
}
first = first.next;
if (second.next != null) {
second = second.next.next;
} else {
second = second.next;
}
}
return false;
}
}
2. 删除链表的倒数第n个节点
class Solution {
public ListNode removeNthFromEnd(ListNode head, int n) {
ListNode newHead = new ListNode();
newHead.next = head;
ListNode fast = head;
ListNode slow = head;
for (int i=1; i<n; i++) {
if (fast != null) {
fast = fast.next;
} else {
return newHead.next;
}
}
ListNode pre = newHead;
while(fast.next!=null){
fast=fast.next;
slow = slow.next;
pre = pre.next;
}
pre.next = slow==null?null:slow.next;
return newHead.next;
}
}
3. 旋转链表
class Solution {
public ListNode rotateRight(ListNode head, int k) {
ListNode f = head;
ListNode s = head;
int l = 0;
ListNode cur = head;
while(cur!=null){
l++;
cur = cur.next;
}
if (l==0) {
return head;
}
int nk = k%l;
if (l==0 || l==1 || nk==0) {
return head;
}
for (int i=1; i<nk; i++) {
f = f.next;
}
ListNode newHead = new ListNode();
newHead.next = head;
ListNode pre = newHead;
while(f.next != null){
f = f.next;
s = s.next;
pre = pre.next;
}
pre.next = null;
f.next = head;
return s;
}
}
删除重复节点类型
分两种情况:一种是将重复元素,全部删除,这种cur要从空节点开始。 一种是将有重复的节点删除到只剩一个节点,这种情况cur应该从head开始。
1. 删除排序链表中的重复元素 II
class Solution {
public ListNode deleteDuplicates(ListNode head) {
ListNode newHead = new ListNode();
newHead.next = head;
ListNode cur = newHead;
while(cur.next != null && cur.next.next != null){
if (cur.next.val == cur.next.next.val) {
int val = cur.next.val;
cur.next = cur.next.next.next;
while (cur.next != null && cur.next.val == val){
cur.next = cur.next.next;
}
} else{
cur = cur.next;
}
}
return newHead.next;
}
}
双链表,有两个指针,一个快,一个慢
1. 合并两个有序链表
class Solution {
public ListNode mergeTwoLists(ListNode list1, ListNode list2) {
ListNode head = new ListNode();
ListNode tail = head;
while(list1 != null && list2 != null){
if (list1.val >= list2.val) {
tail.next = list2;
tail = tail.next;
list2 = list2.next;
} else
{
tail.next = list1;
tail = tail.next;
list1 = list1.next;
}
}
ListNode sub = list1 != null?list1 : list2;
tail.next = sub;
return head.next;
}
}
2. 两数相加
class Solution {
public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
ListNode p1 = l1;
ListNode p2 = l2;
int yushu = 0;
int zhengshu = 0;
ListNode tail = p1;
while (p1 != null || p2 != null) {
int v1 = p1 != null? p1.val:0;
int v2 = p2 != null? p2.val:0;
yushu = (v1 + v2 + zhengshu)%10;
tail.val = yushu;
zhengshu = (v1 + v2 + zhengshu)/10;
if(p1 != null){
p1 = p1.next;
}
if (p2 != null) {
p2 = p2.next;
}
ListNode notNull = (p1 !=null)? p1 : p2;
if (notNull != null) {
tail.next = notNull;
tail = tail.next;
}
}
if (zhengshu != 0) {
ListNode last = new ListNode();
last.val = zhengshu;
tail.next = last;
}
return l1;
}
}
使用递归 + map的方式来解
1. 随机链表的复制
class Solution {
Map<Node, Node> cachedNode = new HashMap<Node, Node>();
public Node copyRandomList(Node head) {
if (head == null) {
return null;
}
if (!cachedNode.containsKey(head)) {
Node headNew = new Node(head.val);
cachedNode.put(head, headNew);
headNew.next = copyRandomList(head.next);
headNew.random = copyRandomList(head.random);
}
return cachedNode.get(head);
}
}
需要使用3个指针,pre,cur,next
1. 翻转链表
class Solution {
public ListNode reverseBetween(ListNode head, int left, int right) {
ListNode cur = head;
ListNode pre = new ListNode();
pre.next = cur;
for (int i = 0; i<left-1;i++) {
cur = cur.next;
pre = pre.next;
}
for (int i=0; i<right - left; i++) {
ListNode next = cur.next;
cur.next = next.next;
next.next = pre.next;
pre.next = next;
}
return head;
}
}
LRU缓存-涉及到的数据结构很多,最好还是使用LinkedHashMap实现
public class Test {
static class LRU extends LinkedHashMap<String, Object>{
private int capability;
public LRU(int capability){
this.capability = capability;
}
@Override
protected boolean removeEldestEntry(HashMap.Entry eldest) {
return this.size() > capability;
}
}
public static void main(String[] args) {
Test t = new Test();
LRU lru = new LRU(3);
lru.put("2","2");
lru.put("1","1");
lru.put("3","3");
for (Map.Entry<String, Object> entry : lru.entrySet()) {
System.out.println(entry.getKey() + ":" + entry.getValue());
}
System.out.println("=====1=====");
lru.put("4","4");
for (Map.Entry<String,Object> entry : lru.entrySet()) {
System.out.println(entry.getKey() + ":" + entry.getValue());
}
System.out.println("====2======");
lru.remove("1");
lru.put("1","1");
for (Map.Entry<String,Object> entry : lru.entrySet()) {
System.out.println(entry.getKey() + ":" + entry.getValue());
}
System.out.println("=====3=====");
lru.put("5","5");
for (Map.Entry<String,Object> entry : lru.entrySet()) {
System.out.println(entry.getKey() + ":" + entry.getValue());
}
int[] ints = {1, 2};
int[] arr = {1,2,3};
int[] arr2 = new int[3];
int[] arr3 = new int[]{1,2,3,4,5};
}
}
只使用一个cur,但是会使用cur.next, cur.next.next, 这种方式适合处理相邻元素的问题
1. 删除排序列表中的重复元素,全部删除
class Solution {
public ListNode deleteDuplicates(ListNode head) {
ListNode newHead = new ListNode();
newHead.next = head;
ListNode cur = newHead;
while(cur.next != null && cur.next.next != null){
if (cur.next.val == cur.next.next.val) {
int val = cur.next.val;
cur.next = cur.next.next.next;
while (cur.next != null && cur.next.val == val){
cur.next = cur.next.next;
}
} else{
cur = cur.next;
}
}
return newHead.next;
}
}
数组
解法综述:
1. 使用双指针的方式来解:
a. 滑动窗口
b. 尾指针 + check指针
c. 快慢指针,用在股票中,如果是上升趋势就累加
d. 三指针
2. 数组如何轮转
3. 利用数组做跳跃玩法
4. 整数转罗马数字
5. 翻转字符串中的单词
6. 加油站
滑动窗口
1. 长度最小的子数组(总和大于目标值)
class Solution {
public int minSubArrayLen(int target, int[] nums) {
int first = 0;
int second = 0;
int min_length = Integer.MAX_VALUE;
int total_val = nums[first];
while(second < nums.length){
if (total_val >= target) {
min_length = Math.min(min_length, second - first+1);
total_val = total_val - nums[first];
first++;
} else {
second++;
if (second < nums.length) {
total_val = total_val + nums[second];
}
}
}
return min_length == Integer.MAX_VALUE ? 0 : min_length;
}
}
2. 无重复字符的最长子串
class Solution {
public int lengthOfLongestSubstring(String s) {
int first =0;
int second = 0;
int length = s.length();
int max_l = 0;
Map<Character, Integer> map = new HashMap<>();
while(second<length){
Character ch = s.charAt(second);
if (map.containsKey(ch)) {
int pos = map.get(ch);
while (first <= pos) {
map.remove(s.charAt(first));
first++;
}
map.put(ch, second);
} else {
map.put(ch, second);
max_l = Math.max(second - first + 1, max_l);
}
second++;
}
return max_l;
}
}
尾指针 + check指针
删除排序数组中的重复项 II
class Solution {
public int removeDuplicates(int[] nums) {
int n = nums.length;
if (n <= 2) {
return n;
}
int slow = 2, fast = 2;
while (fast < n) {
if (nums[slow - 2] != nums[fast]) {
nums[slow] = nums[fast];
++slow;
}
++fast;
}
return slow;
}
}
快慢指针,用在股票中,如果是上升趋势就累加
股票买卖的最佳时机
class Solution {
//思路:马后炮,知道第二天会涨, 所以前一天会买
public int maxProfit(int[] prices) {
if (prices.length == 1) {
return 0;
}
int slow = 0;
int fast = 1;
int total_profit = 0;
while(fast < prices.length){
if (prices[fast] > prices[slow]) {
total_profit = total_profit + prices[fast] - prices[slow];
}
fast++;
slow++;
}
return total_profit;
}
}
数组如何轮转
轮转数组
class Solution {
//思路很巧妙:将翻转问题转为,全局倒序,再局部正序。
public void rotate(int[] nums, int k) {
int real_k = k % nums.length ;
reverse(nums, 0, nums.length - 1);
reverse(nums, 0, real_k - 1);
reverse(nums, real_k, nums.length - 1);
}
public void reverse(int[] nums, int left, int right){
while (left < right) {
int temp = nums[left];
nums[left] = nums[right];
nums[right] = temp;
left++;
right--;
}
}
}
利用数组做跳跃玩法
只能跳到指定位置
class Solution {
//思路:
//1. 确定可以跳到的最大位置,如果没有变大就是false。
//2. 如果可以变大就继续跳,直到最尾部,那就是true
public boolean canJump(int[] nums) {
if (nums.length == 1) {
return true;
}
int cur = 0;
int tail = cur + nums[cur];
// if (nums[cur] == 0) {
// return false;
// }
while (cur<=tail) {
int pos = nums[cur] + cur;
if (pos > tail) {
tail = pos;
}
if (tail >= nums.length - 1) {
return true;
}
cur++;
}
return false;
}
}
可以在一定范围内随意跳
class Solution {
//思路:
//1. 确定可以跳到的最大位置,如果没有变大就是false。
//2. 如果可以变大就继续跳,直到最尾部,那就是true
public boolean canJump(int[] nums) {
if (nums.length == 1) {
return true;
}
int cur = 0;
int tail = cur + nums[cur];
// if (nums[cur] == 0) {
// return false;
// }
while (cur<=tail) {
int pos = nums[cur] + cur;
if (pos > tail) {
tail = pos;
}
if (tail >= nums.length - 1) {
return true;
}
cur++;
}
return false;
}
}
整数转罗马数字
class Solution {
int[] values = {1000, 900, 500, 400, 100, 90, 50, 40, 10, 9, 5, 4, 1};
String[] symbols = {"M", "CM", "D", "CD", "C", "XC", "L", "XL", "X", "IX", "V", "IV", "I"};
public String intToRoman(int num) {
StringBuilder roman = new StringBuilder();
for (int i = 0; i < values.length; ++i) {
int value = values[i];
String symbol = symbols[i];
while (num >= value) {
num -= value;
roman.append(symbol);
}
if (num == 0) {
break;
}
}
return roman.toString();
}
}
反正字符串中的单词
class Solution {
public String reverseWords(String s) {
s = s.trim();
String[] words = s.split("\\s+");
StringBuilder res = new StringBuilder();
for (int i= words.length - 1; i>=0; i--) {
res.append(words[i]);
res.append(" ");
}
return res.toString().trim();
}
}
加油站
class Solution {
public int canCompleteCircuit(int[] gas, int[] cost) {
//思路:转换成累计值最大的点
int sum = 0;
int max = 0;
int ans = 0;
for(int i=gas.length -1; i>=0; i--){
sum = sum + gas[i] - cost[i];
max = Math.max(max, sum);
if (sum >= max) {
ans = i;
}
}
return sum >= 0 ? ans : -1;
}
}
求三数之和
关键:要先排序。排序的优点:1. 可以快速过滤重复值。2. 更深层的遍历,范围会更小
class Solution {
//-4,-1,-1,0,1,2
public List<List<Integer>> threeSum(int[] nums) {
Arrays.sort(nums);
List<List<Integer>> res = new ArrayList<>();
for(int first=0; first<nums.length-2;first++){
if(first > 0 && nums[first] == nums[first - 1]) {
continue;
}
int third = nums.length - 1;
int target = 0 - nums[first];
int second=first + 1;
for( ;second<nums.length-1;second++){
if (second > first + 1 && nums[second] == nums[second-1]) {
continue;
}
// 需要保证 b 的指针在 c 的指针的左侧
while (second < third && nums[second] + nums[third] > target) {
--third;
}
if (second == third) {
continue;
}
if (nums[second] + nums[third] == target){
List<Integer> list = new ArrayList<>();
list.add(nums[first]);
list.add(nums[second]);
list.add(nums[third]);
res.add(list);
}
}
}
return res;
}
}
51万+

被折叠的 条评论
为什么被折叠?



