栈、队列、堆的常用方法
Deque<Integer> stack = new LinkedList<>(); //push(E element): 将元素压入栈顶。 //pop(): 弹出并返回栈顶元素。 //peek(): 返回栈顶元素而不弹出。 //isEmpty(): 检查栈是否为空。 //size(): 返回栈中元素的个数 Queue<Integer> queue = new LinkedList(); //offer(E element): 将元素添加到队列尾部。 //poll(): 弹出并返回队列头部元素。 //peek(): 返回队列头部元素而不弹出。 //isEmpty(): 检查队列是否为空。 //size(): 返回队列中元素的个数 PriorityQueue<Integer> pq = new PriorityQueue(); //offer(E element): 将元素添加到优先队列中。 //poll(): 弹出并返回优先队列中的最小元素。 //peek(): 返回优先队列中的最小元素而不弹出。 //isEmpty(): 检查优先队列是否为空。 //size(): 返回优先队列中元素的个数
Map 和 List的常用方法:
//Map 常用方法: Map<String,Integer> map = new HashMap<>(); //put(K key, V value): 将指定的键值对添加到 Map 中。 //get(Object key): 根据键获取对应的值。 //containsKey(Object key): 检查 Map 中是否包含指定的键。 //remove(Object key): 删除指定键对应的键值对。 //keySet(): 获取 Map 中所有键的集合。 //values(): 获取 Map 中所有值的集合。 //entrySet(): 获取 Map 中所有键值对的集合。 //isEmpty(): 检查 Map 是否为空。 //size(): 获取 Map 中键值对的数量。 //getOrDefault():如果 Map 中包含指定键的映射关系,就返回该键对应的值;否则,返回设定的默认值。 //List 常用方法: //add(E element): 将元素添加到列表末尾。 //add(int index, E element): 在指定位置插入元素。list的索引也是从0开始的 //get(int index): 获取指定位置的元素。 //set(int index, E element): 修改指定位置的元素。 //remove(int index): 删除指定位置的元素。 //remove(Object o): 删除第一次出现的指定元素。 //contains(Object o): 检查列表是否包含指定元素。 //isEmpty(): 检查列表是否为空。 //size(): 获取列表中元素的数量 // Map 示例 Map<String, Integer> map = new HashMap<>(); map.put("apple", 1); map.put("banana", 2); int value = map.get("banana"); // value = 2 boolean containsKey = map.containsKey("apple"); // containsKey = true boolean containsValue = map.containsValue(3); // containsValue = false map.remove("apple"); Set<String> keys = map.keySet(); Collection<Integer> values = map.values(); Set<Map.Entry<String, Integer>> entries = map.entrySet(); // List 示例 List<String> list = new ArrayList<>(); list.add("apple"); list.add(1, "banana"); String element = list.get(1); // element = "banana" list.set(0, "orange"); list.remove(1); int index = list.indexOf("apple"); // index = 0 boolean contains = list.contains("banana"); // contains = false int size = list.size(); // size = 1 list.clear();
数组
时间复杂度是衡量算法性能的重要指标,表示算法运行所需时间与问题规模之间的关系
空间复杂则则是算法运行所需空间和问题规模之间的关系
1.二分法查找元素,将数组按mid划分
public int binarySearch(int[] arr, int target) {
int left = 0;
int right = arr.length - 1;
while(left <= right) { // 这里取小于等于代表区间右端点能取到,若取不到,则left < right
int mid = left + (right - left) / 2;
if(arr[mid] > target) {
right = mid - 1; //相应的这里会是right = mid;
} else if(arr[mid] < target) {
left = mid + 1;
} else {
return mid;
}
}
return -1;
}
2.双指针:快慢指针,左右指针;
快慢指针:利用一个快指针和一个慢指针,在一个for循环里完成两个for循环的工作
pubilc void doublePointer(int[] arr, int target) { //去重数组中的某个重复元素;
int low = 0;
for(int fast = 0; fast < arr.length; fast++) {
if(arr[fast] == target) {
arr[low++] = arr[fast];
}
}
}
左右指针:利用在一重循环里左指针和右指针,将时间复杂度从O(n^2)降到O(n);
pubilc int[] twoSum(int[] arr, int target) { //arr是升序排列的
int left = 0;
int right = arr.length - 1;
while(left < right) {
if(target == arr[left] + arr[right]) {
return new int[]{left,right};
}else if(target < arr[left] + arr[right]) {
right--;
}esle {
left++;
}
}
//若没找到则
return new int[]{-1,-1};
}
滑动窗口的精妙之处在于根据当前子序列和大小的情况,不断调节子序列的起始位置。从而将O(n^2)的暴力解法降为O(n)。
//求解最小连续子序列大于某个值
public int minSubArrayLen(int target, int[] nums) {
//滑动窗口的左右边界
int left = 0;
int right = 0;
int sum = 0;
int len = 0;
int result = nums.length + 1;
for(;right < nums.length; right++) {
sum += nums[right];
while(sum >= target) {
len = right - left + 1;
result = len < result ? len : result;
sum -= nums[left];
left++;
}
}
return result == nums.length + 1 ? 0: result;
}
}
递归的三要素:递归终止条件、递归参数和返回类型、单层递归的逻辑
回溯的三要素:回溯函数的返回值和返回类型、回溯函数终止条件、回溯搜索的遍历过程
//回溯模板 void backtracking(参数) { if (终止条件) { 存放结果; return; } for (选择:本层集合中元素(树中节点孩子的数量就是集合的大小)) { 处理节点; backtracking(路径,选择列表); // 递归 回溯,撤销处理结果 } }
动态规划的4步:Dynamic Programming dp
-
确定dp数组及下标的含义
-
确定递推公式
-
dp数组初始化
-
确定遍历顺序
最长回文子串一类题都是二维动态规划
全排列一类题都是回溯
二叉树的前、中、后序遍历
递归法: List<Integer> res = new ArrayList<>() public List<Integer> orderTravel(TreeNode root) { order(root); return res; } public void order(TreeNode root) { if(root == null) return; res.add(root); order(root.left); order(root.right); //前中后序遍历递归法的区别在于.add的位置 } 迭代法: 前序遍历 //前序遍历:中->左->右 入栈顺序:中->右->左
public List<Integer> orderTravel(TreeNode root) {
List<Integer> res = new ArrayList<>();
if(root == null) {
return res;
}
Deque<TreeNode> stack = new LinkedList<>();
stack.push(root);
while (!stack.isEmpty()) {
TreeNode tmp = stack.pop();
res.add(tmp.val);
if(tmp.right != null) stack.push(tmp.right);
if(tmp.left = null) stack.push(tmp.left);
}
}
中序遍历:左->中->右
public List<Integer> orderTravel(TreeNode root) {
List<Integer> result = new ArrayList<>();
Stack<TreeNode> stack = new Stack<>();
TreeNode curr = root;
while (curr != null || !stack.isEmpty()) {
// 将当前节点及其左子树全部压入栈中
while (curr != null) {
stack.push(curr);
curr = curr.left;
}
// 弹出栈顶节点,并访问它
curr = stack.pop();
result.add(curr.val);
// 转向右子树
curr = curr.right;
}
return result;
}
后序遍历:左->右->中
public List<Integer> postorderTraversal(TreeNode root) {
List<Integer> result = new ArrayList<>();
Stack<TreeNode> stack = new Stack<>();
TreeNode curr = root;
TreeNode prev = null;
while (curr != null || !stack.isEmpty()) {
// 将当前节点及其左子树全部压入栈中
while (curr != null) {
stack.push(curr);
curr = curr.left;
}
// 取出栈顶节点
curr = stack.pop();
// 如果当前节点没有右子节点或者右子节点已经被访问过,则访问当前节点
if (curr.right == null || curr.right == prev) {
result.add(curr.val);
prev = curr;
curr = null;
} else { // 否则,将当前节点重新压入栈,并转向右子树
stack.push(curr);
curr = curr.right;
}
}
return result;
}
层序遍历
List<List<Integer> res> = new ArrayList<>();
public List<Integer> travel(TreeNode root) {
checkFun(root);
return res;
}
publiv void checkFun(TreeNode root) {
if (root == null) return;
Queue<TreeNode> que = new LinkedList<>();
que.offer(root);
while (!queue.isEmpty()) {
List<Integer> resItem = new LinkedList<>();
int len = que.size();
while (len > 0) {
TreeNode tmp = que.poll();
resItem.add(tmp.val);
if (tmp.left != null) que.offer(tmp.left);
if (tmp.right != null) que.offer(tmp.right);
len--;
}
res.add(resItem);
}
}