记录一些题目思路
一 、有关数组的几道题
leetcode 238 除自身以外的乘积
/**
* 思路:每个元素的结果等于左边的元素乘以右边的元素
* @param nums
* @return
*/
public int[] productExceptSelf(int[] nums) {
int k = 1;
int len = nums.length;
int[] res = new int[len];
//计算所有元素左边的乘积
for(int i=0;i<len;i++){
res[i] = k;
k = k*nums[i];
}
//计算右边的乘积
k = 1;
for(int j=len-1;j>=0;j--){
res[j] *= k;
k *= nums[j];
}
return res;
}
leetcode 55 跳跃游戏
/**
* 思路:遍历所有节点,每个节点都有最远可达的地方,如果下一个节点在当前最远可到达范围内,那么看当前节点最远可达是否更远,如果遍历完发现最远可达
* 超过了最后一个位置,那么返回true!
* @param nums
* @return
*/
public boolean canJump(int[] nums) {
if(nums==null) {
return false;
}
int mostJump = 0;
int len = nums.length;
for (int i=0;i<len;i++){
if (i<=mostJump){//当前i可达
mostJump = Math.max(mostJump,i+nums[i]);//更新最远可达
if(mostJump>=len-1) {
return true;
}
}
}
return false;
}
leetcode560 和为K的连续子数组
/**
* 普通区间方法,使用了 前缀求和 的方式减少每次都要计算区间和
* (1)从数组第一位开始计算每一位上的【前缀和】--> preSum即当前下标的前缀和
* (2)求在某个区间[i,j]之间子数组的和时,使用preSum[j+1]-preSum[i]
*/
public int subarraySum2(int[] nums, int k) {
if(nums==null) {
return 0;
}
int count = 0;
int len = nums.length;
int[] preSum = new int[len+1];
//计算前缀和,便于后面计算各区间和
preSum[0] = 0;
//相对于0开始
for(int i=0;i<len;i++){
preSum[i+1] = preSum[i] + nums[i];
}
//O(n^2)查找所有结果
for(int left=0;left<=len;left++){
for(int right=left;right<len;right++){
if(preSum[right+1]-preSum[left] == k) {
count ++;
}
}
}
return count;
}
leetcode31 下一个排列
/**
* 思路:
* (1)从右往左找到第一个a[i-1]<a[i] 的数
* (2)再从a[i-1]的右侧找到一个大于它的最小的数,交换位置
* (3)之后在将a[i-1]右边的所有数倒置
*
*/
public void nextPermutation(int[] nums) {
if (nums==null) {
return;
}
int len = nums.length;
int i = len-1;
//从右往左找到第一个不按逆序排序的数!就是可以进行交换的
while(i>0 && nums[i]<=nums[i-1]){
i--;
}
//若否则证明整个数组是有逆序的了不存在下一个值,直接反转整个数组
if(i!=0 ){
int j = len-1;
while( nums[i-1]>=nums[j] ){//找到右边第一个比它大的最小的值进行交换
j--;
}
int temp = nums[j];
nums[j] = nums[i-1];
nums[i-1] = temp;
}
reverse(nums,i);
}
二、双指针的几道题
leetcode75 颜色分类
public static void sortColors(int[] nums) {
int len ;
if (nums==null || (len = nums.length)==0) {
return;
}
//指向0 的最右边界
int p0 = 0;
//指向2 的最左边界
int p2 = len-1;
for (int curr=0;curr<len;curr++){
if (nums[curr]==1) {
continue;
}
else if (nums[curr]==0) {
int temp = nums[curr];
nums[curr] = nums[p0];
nums[p0] = temp;
p0++;
}else {
int temp = nums[curr];
nums[curr] = nums[p2];
nums[p2] = temp;
p2--;
}
}
}
leetcode 11 乘最多水的容器
/**
* 定义两个指针left和right
* 水量小的指针往内移动可能变大,大的一边往内很大可能变小,因此每次都移动小的一边,计算出每次水量的大小
*/
public int maxArea(int[] height) {
int len ;
if ( height==null || (len = height.length)<2 ) {
return 0;
}
int max = 0;
int right = len - 1;
int left = 0;
while(right>left){
max = Math.max(max,( right - left )*Math.min(height[left],height[right]));
if(height[right]>height[left]) {
left++;
}
else {
right--;
}
}
return max;
}
leetcode15 三数之和
/**
* 双指针的方法:
* (1)排序用以去重,去重的关键在于第一次枚举i和第二次枚举j都不要和上一次的枚举重复;
* (2)O(n^2)的枚举 + 双指针 (这里的双指针体现在当第二层枚举不断增大时,第三层的指针的范围越来越小)
* @param nums
* @return
*/
public List<List<Integer>> threeSum(int[] nums) {
List<List<Integer>> res = new ArrayList<>();
int len ;
if ( (len=nums.length) <3 ) {
return res;
}
//排序是为了去重,默认升序
Arrays.sort(nums);
//循环查找所有三元组
//循环a
for (int i=0;i<len;i++){
//保证一层不重复
if(i>0 && nums[i]==nums[i-1]) {
continue;
}
//循环b
for (int j=i+1;j<len;j++){
//二层不重复
if (j>i+1 && nums[j]==nums[j-1]) {
continue;
}
int target = nums[i] + nums[j];
int third = len-1;
//使用third作为指针从最右边往左移动,直到找到相加为0 的值
while(third>j && target+nums[third]>0){
--third;
}
if (third==j) {
break;
}
if (target+nums[third]==0) {
res.add(Arrays.asList(nums[i],nums[j],nums[third]));
}
}
}
return res;
}
leetcode42 接雨水(困难)
/**
* 思路:
* 定义双指针left和right,并记录左右两边最高柱子left_max,right_max
* 对于位置left而言,它左边最大值一定是left_max,右边最大值“大于等于”right_max;
* 某一时刻,如果left_max<right_max成立,那么左边肯定能存水。无论右边将来
* 会不会出现更大的right_max,都不影响这个结果。
* 所以当left_max<right_max时,我们就希望去处理left下标,反之,我们希望去处理right下标。
*/
public int trap(int[] height) {
int len;
if(height==null || (len = height.length)<3) {
return 0;
};
//左边柱子最高值
int left_max = 0;
//右边柱子最高值
int right_max = len -1;
int left = 0;
int right = len - 1;
int rainwater = 0;
while(left<right){
//左边柱子低,那么左边left是一定可以存到水的,且可以根据left_max计算
if (height[left]<height[right]){
//若左边出现了更高的柱子,那么是存不到水的
if (height[left]>height[left_max]) {
left_max = left;
}
//存水
else if(height[left]<height[left_max]) {
rainwater += height[left_max] - height[left];
}
left++;
}else{//右边水位低,那么右边right是一定可以存到水的,且可以根据right_max计算
if (height[right]>height[right_max]) {
right_max = right;
}
else if(height[right]<height[right_max]) {
rainwater += height[right_max] - height[right];
}
right--;
}
}
return rainwater;
}
三、哈希表的几道题
leetcode49 字母异位词
//将每个词都进行排序之后亏可以使用哈希表判断是否是字母异位词
public List<List<String>> groupAnagrams(String[] strs) {
HashMap<String,List<String>> map = new HashMap<>();
for(int i=0;i<strs.length;i++){
char[] array = strs[i].toCharArray();
Arrays.sort(array);
String s = String.valueOf(array);
if (!map.containsKey(s)) map.put(s,new ArrayList<String>());
map.get(s).add(strs[i]);
}
return new ArrayList<>(map.values());
}
leetcode128 最长连续序列
/**
* 思路:首先使用一个HashSet用于去重,然后遍历整个set,每次都去计算从当前元素开始往后的连续
* 序列。
* 需要注意的是使用 !set.contains(n-1) 避免重复计算
*/
public int longestConsecutive(int[] nums) {
int maxLen = 0;
Set<Integer> set = new HashSet<>();
for(int n:nums){
set.add(n);//使用HashashSet可以去掉重复的
}
for(int n:set){
if( !set.contains(n-1) ){ // 结合下面的curr+1就可以避免重复的计算
int curr = n;
int len = 1;
while( set.contains(curr+1) ){
curr += 1;
++ len;
}
maxLen = Math.max(len,maxLen);
}
}
return maxLen;
}
四、链表的几道题
leetcode2 两数相加
public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
ListNode n1=l1,n2=l2;
ListNode head = new ListNode(0);
ListNode curr = head;
int carry = 0;
while(n1!=null || n2!=null){
int val1 = n1==null?0:n1.val;
int val2 = n2==null?0:n2.val;
int sum = val1 + val2 + carry;
carry = 0;
if (sum >= 10) {
carry = 1;
}
curr.next = new ListNode(sum % 10);
curr = curr.next;
if (n1!=null) {
n1 = n1.next;
}
if (n2!=null) {
n2 = n2.next;
}
}
return head.next;
}
leetcode19 删除链表倒数第n个节点
public ListNode removeNthFromEnd(ListNode head, int n) {
ListNode node = head;
ListNode pre = null;
ListNode curr = head;
//让第一个节点先走 n 步,这样到达终点时curr就是要删除的节点
for (int i=0;i<n-1;i++){
node = node.next;
}
while(node.next!=null){
pre = curr;
curr = curr.next;
node = node.next;
}
if (pre == null) head = head.next;
else pre.next = pre.next.next;
return head;
}
leetcode23 合并k个升序序列
//解法1 :顺序合并
public ListNode mergeKLists(ListNode[] lists) {
if(lists == null) {
return null;
}
int k = lists.length;
boolean over = false;
ListNode res = new ListNode();
ListNode node = res;
while( true ){
int currMin = -1;
over = true;//标识所有链表是否都结束遍历了
//找到下一个最小的节点
for( int i=0;i<k;i++ ){
if( over && lists[i]!=null ){
over = false;
}
if(lists[i]!=null) {
if(currMin == -1) {
currMin = i;
}
else {
currMin = lists[i].val < lists[currMin].val ? i : currMin;
}
}
}
//结束
if (over) {
break;
}
node.next = lists[currMin];
lists[currMin] = lists[currMin].next;
node = node.next;
}
return res.next;
}
//解法2:直接使用优先级队列
public ListNode mergeKLists2(ListNode[] lists) {
if (lists == null) {
return null;
}
PriorityQueue<ListNode> priorityQueue = new PriorityQueue<>(new Comparator<ListNode>() {
@Override
public int compare(ListNode o1, ListNode o2) {
return Integer.compare(o1.val, o2.val);
}
});
int k = lists.length;
ListNode node = null;
for (int i=0;i<k;i++){
node = lists[i];
while( node != null ){
priorityQueue.offer(node);
ListNode n = node;
node = node.next;
n.next = null; // 注意这里要将原链表断开,否则最终结果可能会导致环的出现
}
}
ListNode res = new ListNode();
node = res;
while ( !priorityQueue.isEmpty() ){
node.next = priorityQueue.poll();
node = node.next;
}
return res.next;
}
leetcode142 环形链表
找环的入口,笔试还遇到过这题了。
/**
* 环形链表 -- 环的入口节点
* - 是否有环:快慢指针法
* - 环的入口在哪?
* (1)记录两个快慢指针相遇的位置
* (2)
*/
public ListNode detectCycle(ListNode head) {
ListNode pfast=head;
ListNode pslow=head;
while(pfast!=null){
//慢指针每次走一步
pslow = pslow.next;
//快指针每次走两步
pfast = pfast.next;
if (pfast!=null) {
pfast = pfast.next;
}
//快慢指针相遇,表示存在环
if (pfast==pslow) {
break;
}
}
//若存在环
if (pfast!=null) {
int count = 1;
pfast = pfast.next;
//计算出环的大小count
while(pfast!=pslow){
count++;
pfast = pfast.next;
}
pslow = head;
pfast = head;
for (int i=0;i<count;i++){
pfast = pfast.next;
}
//相遇时,快指针pfast相比慢指针pslow多走了一个环的距离,因此相遇的地方就是环的入口!
while(pfast!=pslow) {
pslow = pslow.next;
pfast = pfast.next;
}
}
return pfast;
}
五、树的几道题
leetcode98 验证二叉搜索树
二叉搜索树的中序遍历是升序的,使用一个pre记录前一个遍历的节点
Integer pre = null;
public boolean isValidBSTCore(TreeNode node) {
if (node==null) {
return true;
}
if (!isValidBSTCore(node.left)) {
return false;
}
if (pre!=null && pre>=node.val) {
return false;
}
//更新pre
pre = node.val;
return isValidBSTCore(node.right);
}
leetcode236 二叉树最近公共祖先
/**
* 精选思路:如果当前节点root 为 p ,q 的最近公共祖先节点,那么存在三种情况:
* (1)p , q 在root的左右子树中,并且都在异侧
* (2)p 为 root
* (3)q 为 root
* 递归思路,使用后序遍历,
* 遇到p 或者 q 时就返回该节点,
* 当超过叶子节点还没有p,q 就返回null
* 左右子树都返回null,则当前节点也返回null,表明当前节点不是公共祖先
* 若其一不为null,表明要么不为最近公共祖先,但是某一个子树包含p,q 两节点
* @param root
* @param p
* @param q
* @return
*/
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
if (root==null) {
return null;
}
if(root.val==p.val || root.val==q.val) {
return root;//遇到p 或者 q 时就直接返回该节点
}
//后序遍历
TreeNode left = lowestCommonAncestor(root.left, p, q);//遍历左子树
TreeNode right = lowestCommonAncestor(root.right, p, q);//遍历右子树
if (left==null && right==null) {
return null;//左右子树都不包含p , q ,返回null
}
if (left==null) {
return right;//左子树中不包含p,q,则p,q肯定在右子树
}
if (right==null) {
return left;//右子树中不包含p,q,则p,q肯定在左子树
}
return root;//最近公共祖先为此!即左右子树包含p , q
}
/**
* 第二种解法,借助HashMap
* 进行一次先序遍历,使用HashMap记录所有节点的父节点
* 从其中一个节点p开始遍向上遍历,记录所有祖先;
* 再从另外一个q开始向上遍历祖先,若某个祖先已被p访问,那这个节点就是最近公共祖先节点
*/
根据两个遍历顺序构造一棵二叉树
leetcode105 根据前序遍历与中序遍历构造二叉树
- 前序遍历可以得到父节点
- 中序遍历根据父节点分成左右树
- 重复上述步骤
public TreeNode buildTree(int[] preorder, int[] inorder) {
if (preorder==null || inorder==null) {
return null;
}
return buildTreeCore(preorder,inorder,0,preorder.length-1,0,inorder.length-1);
}
public TreeNode buildTreeCore(int[] preorder, int[] inorder,int lp,int rp,int li,int ri) {
if (lp>rp || li>ri){
return null;
}
TreeNode node = new TreeNode(preorder[lp]);//根节点
int i = li;//找到中序序列的中值
while(i<=ri && inorder[i]!=preorder[lp]){//找到中序遍历根节点
++i;
}
node.left = buildTreeCore(preorder,inorder,lp+1,lp+i-li,li,i-1);
node.right = buildTreeCore(preorder,inorder,lp+i-li+1,rp,i+1,ri);
return node;
}
根据后序遍历与中序遍历构造二叉树
- 后序遍历也可以得到父节点
- 中序遍历根据父节点分成左右树
- 重复上述步骤
leetcode114 二叉树展开为链表
/**
* 方法一
* 先前序遍历,再转化链表
* @param root
*/
public void flatten(TreeNode root) {
if (root==null) {
return ;
}
ArrayList<TreeNode> list = new ArrayList<>();
travese(list,root);
for (int i=1;i<list.size();i++){
list.get(i-1).right = list.get(i);
list.get(i-1).left = null;
}
root = list.get(0);
}
//前序遍历,记住结果
public void travese(List<TreeNode> list,TreeNode root){
LinkedList<TreeNode> stack = new LinkedList<>();
TreeNode node = root ;
while(node!=null || !stack.isEmpty()){
while(node!=null){
list.add(node);
stack.push(node);
node = node.left;
}
node = stack.pop();
node = node.right;
}
}
/**
* 方法二
* 边前序遍历边转化链表
* (1)进行先序遍历
* (2)需要记住前一个遍历的节点pre,让当前节点作为前一个节点的右子节点,并且令左子节点为null
* (3)让右子节点先入栈,左子节点再入栈。(这么做可以记住右子节点,且下次出栈时只有左子节点先出栈)
* @param root
*/
public void flatten2(TreeNode root) {
if (root==null) {
return ;
}
LinkedList<TreeNode> stack = new LinkedList<>();
stack.push(root);
TreeNode pre = null;
while(!stack.isEmpty()){
TreeNode curr = stack.pop();
if (pre!=null) {
pre.left = null;
pre.right = curr;
}
if (curr.right!=null){
stack.push(curr.right);
}
if (curr.left!=null){
stack.push(curr.left);
}
pre = curr;
}
}
leetcode538 二叉搜索树转化为累加树
/**
* 二叉搜索树左子树的值都小于当前节点,右子树的值都大于当前节点
* 使用Sum记录大于当前数的节点之和
* 使用反序中序遍历,从最大的节点开始累加即可
*/
class Solution {
int sum = 0;
public TreeNode convertBST(TreeNode root) {
if(root!=null){
convertBST(root.right);
sum += root.val;
root.val = sum;
convertBST(root.left);
}
return root;
}
}
六、排序的几道题
leetcode253 会议室II
/**
* 思路:将会议安排按照开始时间排序
* 使用一个优先级队列作为最小堆,有一个新会议进来时去判断堆顶的最先结束的会议是否已经结束,结束了则直接进队,否则开辟新的会议室
*
* 优先级队列 类比 会议室
*/
/**
* 实现快排
*/
public static void quickSort(int[][] intervals,int left,int right){
int l = left;
int r = right;
int pivot = intervals[(l+r)/2][0];
int temp;
while(l<r){
while(intervals[l][0] < pivot){
l++;
}
while(intervals[r][0] > pivot){
r--;
}
if (l>=r) {
break;
}
for(int i=0;i<2;i++){
temp = intervals[r][i];
intervals[r][i] = intervals[l][i];
intervals[l][i] = temp;
}
if(intervals[r][0] == pivot) {
l++;
}
if(intervals[l][0] == pivot) {
r--;
}
}
if (l==r){
l++;
r--;
}
if(left<r) {
quickSort(intervals,left,r);
}
if(right>l) {
quickSort(intervals,l,right);
}
}
public static int minMeetingRooms(int[][] intervals) {
if (intervals==null || intervals.length==0 || intervals[0].length==0) {
return 0;
}
//创建最小堆,堆头是最先结束的会议
PriorityQueue<int[]> priorityQueue = new PriorityQueue<>(new Comparator<int[]>() {
@Override
public int compare(int[] o1, int[] o2) {
return Integer.compare(o1[1],o2[1]);
}
});
int len = intervals.length;
quickSort(intervals,0,len-1);
priorityQueue.add(intervals[0]);
for(int i=1;i<len;i++){
int[] peek = priorityQueue.peek();
//已结束的会议退出会议室(即我们的优先级队列)
if (peek[1]<=intervals[i][0]){
priorityQueue.poll();
}
priorityQueue.add(intervals[i]);
}
return priorityQueue.size();
}
leetcode56 合并区间
/**
* 与253类似题目,核心思想就是如果我们按照区间的左端点排序,那么在排完序的列表中,可以合并的区间一定是连续的
*/
public int[][] merge(int[][] intervals) {
if (intervals==null || intervals.length==0 || intervals[0].length==0) {
return new int[0][0];
}
Arrays.sort(intervals, new Comparator<int[]>() {
@Override
public int compare(int[] o1, int[] o2) {
return Integer.compare(o1[0],o2[0]);
}
});
ArrayList<int[]> list = new ArrayList<>();
int[] temp = intervals[0];
for(int i=1;i<intervals.length;i++){
if(temp[1]>=intervals[i][0]){//注意 等于 时也是存在交界,需要合并!
temp[1] = temp[1]<intervals[i][1]?intervals[i][1]:temp[1];//合并
}else{
list.add(temp);
temp = intervals[i];
}
}
list.add(temp);
int[][] merge = new int[list.size()][2];
int i = 0;
for (int[] t : list){
merge[i++] = t;
}
return merge;
}
leetcode215 数组中的第 K 个最大元素
- 排序 或 直接使用优先级队列
/**
* 基于堆排
*/
public int findKthLargest(int[] nums, int k) {
heapSort(nums);
return nums[k-1];
}
/**
* 堆排序
* @param nums
*/
private void heapSort(int[] nums){
if (nums == null) {
return;
}
int len = nums.length;
for ( int i=len/2-1;i>=0;i-- ){
buildMinHeap(nums,i,len);
}
for (int i=len-1;i>0;i--){
swap(nums,0,i);
buildMinHeap(nums,0,i);
}
}
/**
* 构建最小堆
* @param nums
* @param i
* @param len
*/
private void buildMinHeap(int[] nums,int i,int len){
int temp = nums[i];
for (int k=2*i+1;k<len;k=2*k+1){
if ( k+1<len && nums[k+1]<nums[k] ){
k += 1;
}
if( nums[k] < temp ){
nums[i] = nums[k];
i = k;
}
}
nums[i] = temp;
}
private void swap(int[] nums,int i,int j){
int temp = nums[i];
nums[i] = nums[j];
nums[j] = temp;
}
/**
* 基于快排
*/
public int findKthLargest(int[] nums, int k) {
quickSort(nums,0,nums.length-1);
return nums[k-1];
}
/**
* 快排
*/
private void quickSort(int[] nums,int left,int right){
int l = left,r = right;
int pivot = nums[(l+(r-l)/2)];
while(l<r){
while( nums[l] > pivot ){
++l;
}
while( nums[r] < pivot ){
--r;
}
if ( l >= r ) {
break;
}
swap(nums,l,r);
if ( nums[l] == pivot ){
--r;
}
if ( nums[r] == pivot ){
++l;
}
}
if ( l == r ){
++l;
--r;
}
if (left<r){
quickSort(nums,left,r);
}
if (l<right){
quickSort(nums,l,right);
}
}
/**
* 交换
* @param nums
* @param i
* @param j
*/
private void swap(int[] nums,int i,int j){
int temp = nums[i];
nums[i] = nums[j];
nums[j] = temp;
}
//使用优先级队列
public int findKthLargest(int[] nums, int k) {
PriorityQueue<Integer> queue = new PriorityQueue<>(new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o2-o1;
}
});
for (int i=0;i<nums.length;i++){
queue.add(nums[i]);
}
for (int i=0;i<k-1;i++){
queue.poll();
}
return queue.peek();
}
leetcode347 前K个高频元素
这里也是优先级队列的用法 —— 保证队列中存放着K个最高频的元素。
public int[] topKFrequent(int[] nums, int k) {
int len = nums.length;
HashMap<Integer, Integer> frequent = new HashMap<>();
//小顶堆,用于记录最高频K个数
PriorityQueue<int[]> minHeap = new PriorityQueue<>(new Comparator<int[]>() {
@Override
public int compare(int[] o1, int[] o2) {
return Integer.compare(o1[1], o2[1]);
}
});
//计数每个元素的出现次数
for (int i=0;i<len;i++){
frequent.put(nums[i],frequent.getOrDefault(nums[i],0)+1);
}
for ( Map.Entry<Integer,Integer> entry : frequent.entrySet() ){
int[] temp = {entry.getKey(), entry.getValue()};
if (minHeap.size()<k){
minHeap.add(temp);
}else if (minHeap.peek()[1] < entry.getValue()){
minHeap.poll();
minHeap.add(temp);
}
}
int[] res = new int[k];
for (int i=0;i<k;i++){
res[i] = minHeap.poll()[0];
}
return res;
}
七、设计某种数据结构
leetcode146 LRU 缓存机制
注意一个点就是初始化时,让头尾节点都指向一个没有值的节点,这样子后续插入删除时就不必去对头尾节点做多余的操作。
/**
* 运用你所掌握的数据结构,设计和实现一个 LRU (最近最少使用) 缓存机制。它应该支持以下操作: 获取数据 get 和 写入数据 put 。
* 获取数据 get(key) - 如果关键字 (key) 存在于缓存中,则获取关键字的值(总是正数),否则返回 -1。
* 写入数据 put(key, value) - 如果关键字已经存在,则变更其数据值;如果关键字不存在,则插入该组「关键字/值」。
* 当缓存容量达到上限时,它应该在写入新数据之前删除最久未使用的数据值,从而为新的数据值留出空间。
*
* * 总结:
* * 接用LinkedHashMap的思路,遇到的问题:容量不够用时,忘记删除‘头’节点的值
* * 删除头尾节点时,注意边界情况,头节点.next.next没有值,或者尾节点.next.netx没有值,这时候设置prev会出问题!
* * 双向链表,可以使用空的头尾节点,然后在中间插入各种值!~
*
*/
/**
* 双向链表
*/
class ListNode{
Integer key ;
Integer item ;
ListNode next;
ListNode prev;
public ListNode(Integer item,Integer key) {
this.item = item;
this.key = key;
}
public ListNode() {
}
}
class LRUCache {
private ListNode head;
private ListNode tail;
/**
* 用于存放每个节点链表的位置
*/
private HashMap<Integer,ListNode> map;
/**
* 容量0
*/
private final int CACHE_CAPACITY ;
public LRUCache(int capacity) {
CACHE_CAPACITY = capacity;
map = new HashMap<>(CACHE_CAPACITY);
head = new ListNode();
tail = new ListNode();
tail.prev = head;
head.next = tail;
}
public int get(int key) {
if (!map.containsKey(key)) {
return -1;
}
ListNode node = map.get(key);
//移除
removeNode(node);
//插到头节点
moveToHead(node);
return node.item;
}
public void put(int key, int value) {
ListNode node;
if (map.containsKey(key)){
node = map.get(key);
node.item = value;
//移除
removeNode(node);
}else{
node = new ListNode(value,key);
map.put(key,node);
}
//插到头节点
moveToHead(node);
if (map.size()>CACHE_CAPACITY){//超出容量移除尾节点
map.remove(Integer.valueOf(tail.prev.key));
removeNode(tail.prev);
}
}
private void removeNode(ListNode node){
node.prev.next = node.next;
node.next.prev = node.prev;
}
private void moveToHead(ListNode node){
node.next = head.next;
head.next.prev = node;
head.next = node;
node.prev = head;
}
private void moveToTail(ListNode node){
tail.prev.next = node;
node.prev = tail.prev;
node.next = tail;
tail.prev = node;
}
}
八、回溯法的几道题
回溯法是深度优先搜索的一种.
其实也是一种暴力解…
result = []
def backtrack(路径, 选择列表):
if 满⾜结束条件:
result.add(路径)
return
for 选择 in 选择列表:
做选择
backtrack(路径, 选择列表)
撤销选择
N皇后问题
其实就是一个全排列的问题
//记录一共有多少中排列
static int count = 0;
//第row行的皇后做选择 j = 0 - n
public static void NQueenCount(int[][] matrix, int row, int n) {
//满⾜结束条件
if (row == 8) {
//result.add(路径)
count++;
return;
}
//for 选择 in 选择列表:
for (int j = 0; j < n; j++) {
if (isNotConflict(matrix, row, j, n)) {
//做选择
matrix[row][j] = 1;
//backtrack(路径, 选择列表)
NQueenCount(matrix, row + 1, n);
//撤销选择
matrix[row][j] = 0;
}
}
}
//判断是否发生冲突
public static boolean isNotConflict(int[][] matrix, int i, int j, int n) {
//横向
for (int k = 0; k < n; k++) {
if (matrix[i][k] == 1) return false;
}
//纵向
for (int k = 0; k < n; k++) {
if (matrix[k][j] == 1) return false;
}
//四个对角线方向
for (int k = i, l = j; k < n && l < n; k++, l++) {
if (matrix[k][l] == 1) return false;
}
for (int k = i, l = j; k >= 0 && l >= 0; k--, l--) {
if (matrix[k][l] == 1) return false;
}
for (int k = i, l = j; k >= 0 && l < n; k--, l++) {
if (matrix[k][l] == 1) return false;
}
for (int k = i, l = j; k < n && l >= 0; k++, l--) {
if (matrix[k][l] == 1) return false;
}
return true;
}
九、深度优先遍历的几道题
解决在 【图】中查找路径的问题
// 计算从起点 start 到终点 target 的最近距离
int BFS(Node start, Node target) {
Queue<Node> q; // 核⼼数据结构
Set<Node> visited; // 避免⾛回头路
q.offer(start); // 将起点加⼊队列
visited.add(start);
int step = 0; // 记录扩散的步数
while (q not empty) {
int sz = q.size();
/* 将当前队列中的所有节点向四周扩散 */
for (int i = 0; i < sz; i++) {
Node cur = q.poll();
/* 划重点:这⾥判断是否到达终点 */
if (cur is target) return step;
/* 将 cur 的相邻节点加⼊队列 */
for (Node x : cur.adj()){/* cur.adj() 泛指 cur 相邻的节点 */
if (x not in visited) {
q.offer(x);
visited.add(x);
}
}
}
/* 划重点:更新步数在这⾥ */
step++;
}
}
leetcode301 删除无效括号(困难)
/**
* 思路:
* (1)首先遍历字符串,判断需要删除的左括号或者右括号的数量,分别是left_rem和right_rem --> 所有删除最少无效括号的结果 都是 删除这个数量的括号
* (2)回溯时则根据是否已经删除了所有多出来的括号,来判断是否添加到结果集中
*
* 对于普通的回溯,是不知道哪些括号放错了位置的,因为回溯时会尝试移除每一个括号,最后再去得到删除最少无效括号的结果;
* 而优化后的回溯,已知需要移除的括号的数量,这样也就不必在加入结果集时判断删除数量的多少了。
*/
private Set<String> validExpressions = new HashSet<String>();
//深度优先遍历
private void recurse(
String s,
int index,
int leftCount,
int rightCount,
int leftRem,
int rightRem,
StringBuilder expression) {
if (index == s.length()) {
//所有左右括号刚好匹配,加入结果集中!
if (leftRem == 0 && rightRem == 0) {
this.validExpressions.add(expression.toString());
}
} else {
char character = s.charAt(index);
int length = expression.length();
//若 左括号或者右括号 需要删除的数量 大于0 时,删除当前括号
if ((character == '(' && leftRem > 0) || (character == ')' && rightRem > 0)) {
this.recurse(
s,
index + 1,
leftCount,
rightCount,
leftRem - (character == '(' ? 1 : 0),//删除当前左括号
rightRem - (character == ')' ? 1 : 0),//删除当前右括号
expression);
}
//回溯:不删除当前括号
expression.append(character);
// 不为左括号也不为右括号,直接加入结果expression中
if (character != '(' && character != ')') {
this.recurse(s, index + 1, leftCount, rightCount, leftRem, rightRem, expression);
} else if (character == '(') {
this.recurse(s, index + 1, leftCount + 1, rightCount, leftRem, rightRem, expression);
} else if (rightCount < leftCount) {//等于右括号 且 右括号数量小于左括号
this.recurse(s, index + 1, leftCount, rightCount + 1, leftRem, rightRem, expression);
}
//移除当前元素,继续回溯
expression.deleteCharAt(length);
}
}
public List<String> removeInvalidParentheses(String s) {
int left = 0, right = 0;
//找出需要删除的右括号和左括号数量
for (int i = 0; i < s.length(); i++) {
//计算假设需要删除的左括号数
if (s.charAt(i) == '(') {
left++;
} else if (s.charAt(i) == ')') {
// 如果遇到右括号时发现没有与之对应的最括号,那么该右括号时需要删除的
right = left == 0 ? right + 1 : right;
// 匹配一个右括号,假设需要删除的左括号数减1
left = left > 0 ? left - 1 : left;
}
}
this.recurse(s, 0, 0, 0, left, right, new StringBuilder());
return new ArrayList<String>(this.validExpressions);
}
leetcode207 课程表
/**
* 思路:
* 找到入度为0 的课程,也就是当前可以上的课,加入队列
* 然后出队,将以我为前置课程的其他节点(课程)的入度减一,若减完之后入度为0,则重新加入队列
*
* 类似BFS算法,因此使用广度优先遍历
* @param numCourses
* @param prerequisites
* @return
*/
public static boolean canFinish(int numCourses, int[][] prerequisites) {
//用于记录节点依赖关系,即记录每一门课所依赖的前置课程都有哪些
List<List<Integer>> dependencies = new ArrayList<>();
//用于记录入度,表示每一门课需要学习的前置课程数量
int[] rudu = new int[numCourses];
for (int i=0;i<numCourses;i++){
dependencies.add(new ArrayList<>());
}
for (int i=0;i<numCourses;i++){
++rudu[prerequisites[i][0]];
//记录我是谁的入度,学习(删掉)我之后,将这些以我为入度的节点的入度减1!
dependencies.get(prerequisites[i][1]).add(prerequisites[i][0]);
}
//进行广度优先遍历的队列
LinkedList<Integer> queue = new LinkedList<>();
for (int i=0;i<numCourses;i++){//将入度为0的课程加入队列
if (rudu[i]==0) {
queue.offer(i);
}
}
while (!queue.isEmpty()){
Integer pre = queue.poll();
--numCourses;
for (Integer curr : dependencies.get(pre)){
if (--rudu[curr]==0) {
queue.addLast(curr);
}
}
}
return numCourses==0;
}
leetcode200 岛屿数量
/**
* 使用BFS广度优先算法
* 将二维矩阵看成是一个无向图,竖直或水平相邻的 111 之间有边相连
* 遇到 1 时将其置为 0,并搜索与之相连的所有节点
*/
public int numIslands(char[][] grid) {
int rowLen = 0;
int cowLen = 0;
int islandNums = 0;//记录岛屿个数
if(grid==null || (rowLen=grid.length)<=0 || (cowLen=grid[0].length)<=0 ) return 0;
for (int i=0;i<rowLen;i++){
for (int j=0;j<cowLen;j++){
if (grid[i][j]=='1'){
LinkedList<Integer> queue = new LinkedList<>();
queue.offerLast(i*cowLen+j);//这种记录二维矩阵坐标的做法可以借鉴!还有下面获取坐标的方法
while (!queue.isEmpty()){
Integer poll = queue.poll();
//获取坐标的方法
int r = poll / cowLen;
int c = poll % cowLen;
if(r-1>=0 && grid[r-1][c]=='1'){
grid[r-1][c] = '0';
queue.offerLast((r-1)*cowLen+c);
}
if(r+1<rowLen && grid[r+1][c]=='1'){
grid[r+1][c] = '0';
queue.offerLast((r+1)*cowLen+c);
}
if(c-1>=0 && grid[r][c-1]=='1'){
grid[r][c-1] = '0';
queue.offerLast(r*cowLen+c-1);
}
if(c+1<cowLen && grid[r][c+1]=='1'){
grid[r][c+1] = '0';
queue.offerLast(r*cowLen+c+1);
}
}
islandNums++;//增加岛屿个数
}
}
}
return islandNums;
}
十、动态规划的几道题
留个空回头补上