title: 左神刷题
date: 2018-09-21 17:32:47
updated: 2020-02-22 14:41:00
categories: 算法刷题
tags:
- 算法刷题
此文档包含左神书上题目(未完结,只做了部分)以及所涉及的leetcode题目,尽量都留存了最优解,部分简单的题目没有进行总结。左神的书——《程序员代码面试指南》
编号 | 题目 |
---|---|
1 | 按照左右半区的方式重新组合单链表 |
2 | 用递归函数和栈操作逆序栈 |
3 | 猫狗队列 |
4 | 用一个栈来实现另一个栈的排序 |
5 | 汉诺塔问题 |
6 | 构造数组的MaxTree |
7 | 最大子矩阵(直方图-栈) |
8 | 找两个排序数组的中位数 |
9 | 删除链表中的节点(两道) |
10 | 找到/删除链表的中间节点(两道) |
11 | 删除链表a/b处的节点 |
12 | 判断一个链表是否为回文结构 |
13 | 将单链表按某值划分成左边小,中间相等,右边大 |
14 | 单链表的归并排序 |
15 | 单链表的快速排序 |
16 | 单链表的选择排序 |
17 | 两个链表生成相加链表 |
18 | 删除单链表的重复节点(三道) |
19 | 向有序的环形单链表中插入新节点 |
20 | 打印二叉树的边界节点 |
21 | 编辑距离 |
22 | 找出数组中左边比他小右边比他大的所有元素 |
23 | 判断二叉树为BST(二叉搜索树) |
24 | Replace Words(字典树应用) |
25 | BFS/DFS思想 |
26 | BFS相关题目(1道) |
27 | DFS相关题目(4道) |
28 | 图的几种最短路算法(4道) |
29 | Word Ladder(2道)(SPFA最短路算法应用) |
30 | 字符串匹配(kmp算法) |
31 | 两数相除(转为减法) |
32 | sqrt(x) |
33 | 乱序的数组中找到最长的递增子序列 |
34 | 蓄水池问题 |
35 | 满足指定sum条件的长度最小的子数组 |
36 | 最大值减去最小值小于或等于num的子数组数量 |
37 | 几道未整理的 |
《左神》86、牛客网有编程挑战题
给定一个单链表的头部节点head,链表长度为N。 如果N为偶数,那么前N/2个节点算作左半区,后N/2个节点算作右半区; 如果N为奇数,那么前N/2个节点算作左半区,后N/2+1个节点算作右半区; 左半区从左到右依次记为L1->L2->…,右半区从左到右依次记为R1->R2->…。请将单链表调整成L1->R1->L2->R2->…的样子。
1->2->3->4 调整后:1->3->2->4
1->2->3->4->5 调整后:1->3->2->4->5
要求:如果链表长度为N,时间复杂度请达到O(N),额外空间复杂度请达到O(1)
思路:遍历两遍链表,先找到左半区的最后一个节点,再左右半区重新拼接链表。时间O(N),空间O(1)
public static void relocateList(ListNode head) {
if(head==null || head.next==null || head.next.next==null || head.next.next.next==null){
return;
}
ListNode guard = new ListNode(0);//哨兵
guard.next = head;
ListNode left = guard.next;//左半区
ListNode right;//左半区的最后一个节点
//1.遍历一遍链表,找到左半区的最后一个节点
ListNode fast = guard;
ListNode low = guard;
while(fast.next!=null && fast.next.next!=null){//fast走两步,low走一步
fast = fast.next.next;
low = low.next;
}
right = low;//找到了左半区的最后一个节点
//2.再左右半区重新拼接链表
ListNode rightCopy = right;//缓存左半区的最后一个节点
while(left!=rightCopy){
//1.剥离当前节点
ListNode temp = right.next;
right.next = temp.next;
temp.next = null;
//2.拼接左右半区节点
temp.next = left.next;
left.next = temp;
left = left.next.next;
}
}
《左神》8
题目:一个栈依次压入1,2,3,4,5那么从栈顶到栈底分别为5,4,3,2,1。将这个栈转置后,从栈顶到栈底为1,2,3,4,5,也就是实现了栈中元素的逆序,请设计一个算法实现逆序栈的操作,但是只能用递归函数来实现,而不能用另外的数据结构。
思路:两个递归函数搞定
import java.util.Stack;
//用递归函数和栈操作逆序栈
public class ReverseStack {
//逆序栈元素
public void reverseStackRecursively(Stack<Integer> stack) {
if(stack.isEmpty()){
return;
}
int bottom = getAndDelBottom(stack);
reverseStackRecursively(stack);
stack.push(bottom);
}
//删除并返回栈底元素
public int getAndDelBottom(Stack<Integer> stack){
int curData = stack.pop();
if(stack.isEmpty()){
return curData;
}
else{
int last = getAndDelBottom(stack);
stack.push(curData);
return last;
}
}
}
《左神》10
题目:实现一种狗猫队列的结构,要求如下:
(1)用户可以调用add方法将cat类或dog类的实例放入队列中;
(2)用户可以调用pollAll方法,将队列中所有的实例按照进队列的先后顺序依次弹出;
(3)用户可以调用pollDog方法,将队列中dog类的实例按照进队列的先后顺序依次弹出;
(4)用户可以调用pollCat方法,将队列中cat类的实例按照进队列的先后顺序依次弹出;
(5)用户可以调用isEmpty方法,检查队列中是否还有dog或cat的实例;
(6)用户可以调用isDogEmpty方法,检查队列中是否还有dog类的实例;
(7)用户可以调用isCatEmpty方法,检查队列中是否还有cat类的实例。
思路:新定义带时间戳的Pet类(PetEnterQue),然后用两个队列(dogQue\catQue)结合时间戳来定义新类(dogCatQue),猫狗队列就是这个类的一个实例
import java.util.LinkedList;
import java.util.Queue;
/**
* 猫狗队列。
*/
public class Pet {
private String type;
public Pet(String type) {
this.type = type;
}
public String getPetType() {
return type;
}
}
public class Dog extends Pet{
public Dog() {
super("dog");
}
}
public class Cat extends Pet{
public Cat() {
super("cat");
}
}
//新定义一个带时间戳的Pet类
public class PetEnterQue{
private Pet pet;
private long count; //时间戳
public PetEnterQue(Pet pet, long count) {
this.pet = pet;
this.count = count;
}
public Pet getPet() {
return pet;
}
public long getCount() {
return count;
}
}
//这个类的实例就是一个猫狗队列
public class dogCatQue{
private Queue<PetEnterQue> dogQue;
private Queue<PetEnterQue> catQue;
private long count;
public dogCatQue(){
dogQue = new LinkedList<>();
catQue = new LinkedList<>();
count = 0;
}
public void offer(Pet pet){
if(pet.getPetType().equals("dog")){
dogQue.offer(new PetEnterQue(pet, count++));
}
else{
catQue.offer(new PetEnterQue(pet, count++));
}
}
public Pet pollDog(){
if(dogQue.isEmpty()){
return null;
}
return dogQue.poll().getPet();
}
public Pet pollCat(){
if(catQue.isEmpty()){
return null;
}
return catQue.poll().getPet();
}
public Pet pollAll(){
if(dogQue.isEmpty()){
if(!catQue.isEmpty()){
return catQue.poll().getPet();
}
}
else if(catQue.isEmpty()){
if(!dogQue.isEmpty()){
return dogQue.poll().getPet();
}
}
else{//比较时间戳,将早进队列的Pet出队
return dogQue.peek().getCount()>catQue.peek().getCount()? catQue.poll().getPet() :dogQue.poll().getPet();
}
return null;
}
public boolean isEmpty(){
return dogQue.isEmpty() && catQue.isEmpty();
}
public boolean isDogEmpty(){
return dogQue.isEmpty();
}
public boolean isCatEmpty(){
return catQue.isEmpty();
}
}
《左神》13
题目:在一个栈中元素的类型为整型,现在想将该栈从栈顶到栈底按从大到小的顺序排序,只许申请一个栈,除此之外,可以申请其他变量,但是不能申请额外的数据结构。
//用一个栈来实现另一个栈的排序
public static void sortStackByStack(Stack<Integer> stack){
Stack<Integer> help = new Stack<>();
while(!stack.isEmpty()){
int temp = stack.pop();
while(!help.isEmpty() && temp>help.peek()){
stack.push(help.pop());
}
help.push(temp);
}
while(!help.isEmpty()){
stack.push(help.pop());
}
}
public static void main(String[] args){
Stack<Integer> stack = new Stack<>();
stack.push(4);
stack.push(3);
stack.push(76);
stack.push(5);
stack.push(8);
stack.push(9);
System.out.println(stack);
sortStackByStack(stack);
System.out.println(stack);
}
法一递归(常用这个):
//汉诺塔tower of hanoi问题。法一:递归
public static void towerOfHanoi(int n) {
move("left", "right", "mid", n);
}
public static void move(String start, String end, String buffer, int n){
if(n==1){
System.out.println("from " + start + " to " + end);
return;
}
move(start, buffer, end, n-1); //将1-n-1移动到buffer,即缓冲区
System.out.println("from " + start + " to " + end); //将n移动到目标柱子上
move(buffer, end, start, n-1); // 将1-n-1移动到目标柱子上
}
public static void main(String[] args){
towerOfHanoi(3);
//out:
//from left to right
//from left to mid
//from right to mid
//from left to right
//from mid to left
//from mid to right
//from left to right
}
法二用栈:
public class HanoiStack {
public static void main(String[] args) {
Stack hanoi = new Stack();
hanoi.push(new Problem(4, 'A', 'B', 'C'));
Problem myProblem = null;
while (!hanoi.isEmpty() && (myProblem = (Problem) hanoi.pop()) != null) {
if (myProblem.n == 1) {
System.out.println(myProblem.A+"->"+myProblem.C);
} else {
hanoi.push(new Problem(myProblem.n-1, myProblem.B, myProblem.A, myProblem.C));
hanoi.push(new Problem(1, myProblem.A, myProblem.B, myProblem.C));
hanoi.push(new Problem(myProblem.n-1, myProblem.A, myProblem.C, myProblem.B));
}
}
}
}
class Problem {
int n;
char A, B, C;
public Problem(int n, char A, char B, char C) {
this.n = n;
this.A = A;
this.B = B;
this.C = C;
}
}
《左神》14
法一递归:
//法一:递归。
public static int hanoiProblem1(int num, String left, String mid, String right){
if(num<1){
return 0;
}
return process(num, left, mid, right, left, right);
}
public static int process(int num, String left, String mid, String right, String from, String to){
if(num==1){
if(from.equals(mid) || to.equals(mid)){
System.out.println("move 1 from " + from + " to " + to);
return 1;
}
else{
System.out.println("move 1 from " + from + " to mid");
System.out.println("move 1 from mid to " + to);
return 2;
}
}
else{
if(from.equals(mid) || to.equals(mid)){
String another = (from.equals(left)||to.equals(left))? right : left;
int step1 = process(num-1, left, mid, right, from, another);
System.out.println("move " + num + " from " + from + " to " + to);
int step2 = 1;
int step3 = process(num-1, left, mid, right, another, to);
return step1 + step2 + step3;
}
else{
int step1 = process(num-1, left, mid, right, from, to);
int step2 = 1;
System.out.println("move " + num + " from " + from + " to mid");
int step3 = process(num-1, left, mid, right, to, from);
int step4 = 1;
System.out.println("move " + num + " from mid to " + to);
int step5 = process(num-1, left, mid, right, from, to);
return step1 + step2 + step3 + step4 + step5;
}
}
}
法二:栈。
非递归的方法核心思想:
1.由于必须经过中间,把三个柱子想成三个栈,每次操作栈顶的一个元素,只有四个动作L->M、M->L、M->R、R->M
2.最优步骤时,每次四个动作只有一个动作能同时满足3、4两个原则,因为满足3、4时直接进行该步骤即可,直到最终第三个栈元素都移过去结束
3.每次移动元素时,栈顶元素小压大
4.每次移动元素时,与上一次移动操作不可互逆(那就是重复无意义的操作)
import java.util.Stack;
enum Action{
No, LToM, MToL, RToM, MToR
}
public class Solution2{
//法二:栈
public static int hanoiProblem2(int num, String left, String mid, String right){
if(num<1){
return 0;
}
Stack<Integer> ls = new Stack<>();
Stack<Integer> ms = new Stack<>();
Stack<Integer> rs = new Stack<>();
ls.push(Integer.MAX_VALUE);
ms.push(Integer.MAX_VALUE);
rs.push(Integer.MAX_VALUE);
for(int i=num; i>=1; i--){
ls.push(i);
}
Action[] record = {Action.No};//record[0]存储上一次操作
int res = 0;
while(rs.size()!=num+1){
res += fstack_To_tStack(record, Action.LToM, Action.MToL, ls, ms, left, mid);
res += fstack_To_tStack(record, Action.MToL, Action.LToM, ms, ls, mid, left);
res += fstack_To_tStack(record, Action.RToM, Action.MToR, rs, ms, right, mid);
res += fstack_To_tStack(record, Action.MToR, Action.RToM, ms, rs, mid, right);
}
return res;
}
private static int fstack_To_tStack(Action[] record, Action nowAct, Action nowActReverse, Stack<Integer>fstack, Stack<Integer>tstack, String from, String to){
if(record[0]!=nowActReverse && fstack.peek()<tstack.peek()){//和上次操作不互逆 且 栈顶满足小压大,即为当前应该走的一步
tstack.push(fstack.pop());
System.out.println("move " + tstack.peek() + " from " + from + " to " + to);
record[0] = nowAct;
return 1;
}
return 0;
}
public static void main(String[] args){
// System.out.println(hanoiProblem1(2,"left", "mid", "right"));//法一:递归
System.out.println(hanoiProblem2(2,"left", "mid", "right"));//法二:栈
}
}
leetcode 654、《左神》22
题目:对于一个没有重复元素的整数数组,请用其中元素构造一棵MaxTree。MaxTree定义为一棵二叉树,其中的节点与数组元素一一对应,同时对于MaxTree的每棵子树,它的根的元素值为子树的最大值。
思路:现有一建树方法,对于数组中的每个元素,其在树中的父亲为数组中它左边比它大的第一个数和右边比它大的第一个数中更小的一个。若两边都不存在比它大的数,那么它就是树根。请设计O(n)的算法实现这个方法。
核心转化为了怎么找到左边/右边第一个比它大的数呢?用栈,递减序列即可。
以找每个数左边第一个比他大的数为例,从左到右遍历每个数,栈中保持递减序列,新来的数不停的Pop出栈顶直到栈顶比新数大或没有数。以[3,1,2]为例,首先3入栈,接下来1比3小,无需pop出3,1入栈,并且确定了1往左第一个比他大的数为3。接下来2比1大,1出栈,2比3小,2入栈。并且确定了2往左第一个比他大的数为3。用同样的方法可以求得每个数往右第一个比他大的数。时间复杂度O(n),空间复杂度也是O(n)为最优解法。
本题最巧妙的一点是如何找某数的左边最近的比它大的值和右边最近的比它大的值。取左右两边的较小的数作为该数的父节点。
import java.util.Stack;
class Node{
public int value;
public Node left;
public Node right;
public Node(int data){
this.value = data;
}
}
public class Solution {
//构造数组的MaxTree。核心:如何找某数的左边最近的比它大的值和右边最近的比它大的值(辅助栈和数组)。取左右两边的较小的数作为该数的父节点。
public static Node makeMaxTree(int[] arr) {
if(arr==null || arr.length==0){
return null;
}
if(arr.length==1){
return new Node(arr[0]);
}
Stack<Integer> stack = new Stack<>();
int[] lmax = new int[arr.length];//存储找出某数的左边最近的比它大的值的索引
int[] rmax = new int[arr.length];//存储找出某数的右边最近的比它大的值的索引
for(int i=0; i<arr.length; ++i){//从左往右遍历,找出某数的左边最近的比它大的值。栈递减序列
while(!stack.isEmpty() && arr[stack.peek()]<arr[i]){
stack.pop();
}
lmax[i] = !stack.isEmpty() ? stack.peek() : -1;//左边最近的比它大的值,如果左边没有比它大的,则为-1
stack.push(i);
}
stack.clear();//清空栈
for(int i=arr.length-1; i>=0; --i){//从右往左遍历,找出某数的右边最近的比它大的值。栈递减序列
while(!stack.isEmpty() && arr[stack.peek()]<arr[i]){
stack.pop();
}
rmax[i] = !stack.isEmpty() ? stack.peek() : -1;//左边最近的比它大的值,如果左边没有比它大的,则为-1
stack.push(i);
}
//两个数组(左边首个最大、右边首个最大)存储好了,取较小的节点作为父节点,开始构建maxTree
Node[] nodes = new Node[arr.length];
for(int i=0; i<nodes.length; ++i){
nodes[i] = new Node(arr[i]);
}
Node head = null;
for(int i=0; i<arr.length; ++i){
if(lmax[i]==-1 && rmax[i]==-1){
head = nodes[i];
}
else if(lmax[i]==-1 && rmax[i]!=-1){
if(nodes[rmax[i]].left == null){
nodes[rmax[i]].left = nodes[i];
}
else{
nodes[rmax[i]].right = nodes[i];
}
}
else if(rmax[i]==-1 && lmax[i]!=-1){
if(nodes[lmax[i]].right == null){
nodes[lmax[i]].right = nodes[i];
}
else{
nodes[lmax[i]].left = nodes[i];
}
}
else if(arr[lmax[i]]<arr[rmax[i]]){
if(nodes[lmax[i]].right == null){
nodes[lmax[i]].right = nodes[i];
}
else{
nodes[lmax[i]].left = nodes[i];
}
}
else if(arr[lmax[i]]>arr[rmax[i]]){
if(nodes[rmax[i]].left == null){
nodes[rmax[i]].left = nodes[i];
}
else{
nodes[rmax[i]].right = nodes[i];
}
}
}
return head;
}
public static void main(String[] args){
int[] arr = new int[]{3,1,2};
System.out.println(makeMaxTree(arr));
}
}
1.Largest Rectangle in Histogram(直方图面积)--leetcode 84
2.Maximal Rectangle(最大子矩阵大小)--leetcode85、《左神》26
leetcode 84
题目:Given n non-negative integers representing the histogram’s bar height where the width of each bar is 1, find the area of largest rectangle in the histogram.
Above is a histogram where width of each bar is 1, given height = [2,1,5,6,2,3]. out: 10
思路:辅助单调栈,递增存储。遍历原直方图数组,递增往栈里存储,如果遇到比栈顶小的元素时,循环栈顶弹出并计算resMax,直到继续递增存储。时间o(n),空间o(n)
public int largestRectangleArea(int[] heights) {
if(heights==null || heights.length==0){
return 0;
}
if(heights.length==1){
return heights[0];
}
Stack<Integer> stack = new Stack<>();//单调栈,递增存储,每次遇到比栈顶小的元素时,对栈进行判断
int resMax = 0;
for(int i=0; i<heights.length; ++i){
while(!stack.isEmpty() && heights[i]<heights[stack.peek()]){//每次遇到比栈顶小的元素时,对栈进行判断
int j = stack.pop();
int k = stack.isEmpty() ? -1 : stack.peek();
resMax = Math.max(resMax, (i-k-1)*heights[j]);
}
stack.push(i);//递增存储
}
while(!stack.isEmpty()){//考虑[1,2,3]的情况,栈一直递增存储,最后必须要清空栈
int j = stack.pop();
int k = stack.isEmpty() ? -1 : stack.peek();
resMax = Math.max(resMax, (heights.length-k-1)*heights[j]);
}
return resMax;
}
leetcode85、《左神》26
题目:Given a 2D binary matrix filled with 0’s and 1’s, find the largest rectangle containing only 1’s and return its area.
1 0 1 0 0
1 0 1 1 1
1 1 1 1 1
1 0 0 1 0
Return 6.
思路:一行一行进行,对矩阵每一行为底的直方图数组求解直方图最大面积,直方图最大面积:用单调递增栈。
public int maximalRectangle(char[][] matrix) {
if(matrix==null || matrix.length==0){
return 0;
}
int m = matrix.length;
int n = matrix[0].length;
if(n==0){
return 0;
}
int res = 0;
int[] temp = new int[n+1];//以矩阵每一行为底的直方图数组,最后一个元素temp[n]要为0,便于求解直方图面积
for(int i=0; i<m; ++i){//一行一行处理直方图的最大面积
for(int j=0; j<n; ++j){//每一行先更新直方图
if(matrix[i][j] == '0'){
temp[j] = 0;
}else{
temp[j]++;
}
}
res = Math.max(res, largeArea(temp)); //直方图最大面积
}
return res;
}
//求一行的直方图最大面积--单调递增栈
public int largeArea(int[] heights){
if(heights.length==1){
return heights[0];
}
Stack<Integer> stack = new Stack<>();//单调栈,递增存储,每次遇到比栈顶小的元素时,对栈进行判断
int resMax = 0;
for(int i=0; i<heights.length; ++i){
while(!stack.isEmpty() && heights[i]<heights[stack.peek()]){//每次遇到比栈顶小的元素时,对栈进行判断
int j = stack.pop();
int k = stack.isEmpty() ? -1 : stack.peek();
resMax = Math.max(resMax, (i-k-1)*heights[j]);
}
stack.push(i);//递增存储
}
return resMax;
}
leetcode 4
题目:给定数组arr和整数num,共返回有多少个子数组满足子数组的最大值-最小值<=num
//Median of Two Sorted Arrays找两个排序数组的中位数。思路:二分法,核心是注意怎么二分和边界条件
public double findMedianSortedArrays(int[] nums1, int[] nums2) {
int n = nums1.length;
int m = nums2.length;
int left = (n + m + 1) / 2;
int right = (n + m + 2) / 2;
return 0.5 * (getKth(nums1,0,n-1,nums2,0,m-1,left) + getKth(nums1,0,n-1,nums2,0,m-1,right));
}
private double getKth(int[] nums1, int start1, int end1, int[] nums2, int start2, int end2, int k){
int len1 = end1 - start1 + 1;
int len2 = end2 - start2 + 1;
if(len1 > len2){
return getKth(nums2,start2,end2,nums1,start1,end1,k);
}
if(len1 == 0) return nums2[start2 + k - 1];
if(k == 1) return Math.min(nums1[start1],nums2[start2]);
int s1 = Math.min(start1 + k/2 - 1, start1 + len1 - 1);
int s2 = Math.min(start2 + k/2 - 1, start2 + len2 - 1);//两个数组的k/2位置上的数,如果数组长度小于k/2,直接比len-1上的数
if(nums1[s1] < nums2[s2]){//nums1数组的前k/2个数不要了,下次k=k-k/2或者k=k-len1/2
return getKth(nums1,s1+1,end1,nums2,start2,end2,k-Math.min(k/2,len1));
}else{//nums2数组的前k/2个数不要了,下次k=k-k/2或者k=k-len1/2
return getKth(nums1,start1,end1,nums2,s2+1,end2,k-Math.min(k/2,len2));
}
}
1.删除某个链表中指定的(非末尾)节点--leetcode237、《左神》83
2.删除链表中等于给定值 val 的所有节点--leetcode203
leetcode237、《左神》83
思路:后一个节点的val覆盖当前节点,删除后一个节点即可
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) { val = x; }
* }
*/
class Solution {
public void deleteNode(ListNode node) {
node.val = node.next.val;
node.next = node.next.next;
}
}
leetcode203、《左神》73
输入: 1->2->6->3->4->5->6, val = 6
输出: 1->2->3->4->5
思路:遍历链表删除即可。
public ListNode removeElements(ListNode head, int val){
if(head==null){
return null;
}
ListNode resHead = new ListNode(0); //哨兵
resHead.next = head;
ListNode p = resHead;
ListNode cur = p.next;
while(cur!=null){
if(cur.val==val){
p.next = cur.next;//这种删除思想特别好
}
else{
p = cur;
}
cur = cur.next;
}
return resHead.next;
}
1.找到链表的中间节点--leetcode 876
2.删除链表的中间节点--《左神》38
leetcode 876
题目:Given a non-empty, singly linked list with head node head, return a middle node of linked list.
If there are two middle nodes, return the second middle node.
Input: [1,2,3,4,5]
Output: Node 3 from this list (Serialization: [3,4,5])
The returned node has value 3.
[1,2,3,4,5,6]
Output: Node 4 from this list (Serialization: [4,5,6])
思路:两个指快慢指针走即可。
public ListNode middleNode(ListNode head) {
if(head.next==null){
return head;
}
ListNode right = head;
ListNode left = head;
while(right.next!=null && right.next.next!=null){
right = right.next.next;
left = left.next;
}
return right.next==null ? left : left.next;
}
《左神》38.
题目:删除一个链表的中间节点,当链表只有一个节点的时候或者head节点为空的时候返回head,当链表有两个节点的时候删除第一个节点,当链表有三个节点的时候删除第二个节点,当链表有四个节点的时候删除第二个节点,当链表有五个节点的时候删除第三个节点…
思路:一个链表长度每增加二,要删除的节点就后移一个节点,要删除一个节点需要知道它的前一个节点。
//删除链表中间节点。思路:两个快慢指针走即可
public static ListNode removeMidNode(ListNode head){
if(head==null || head.next==null){//空节点或只有一个节点
return head;
}
if(head.next.next==null){//两个节点,删除第一个节点
head = head.next;
return head;
}
ListNode right = head.next.next;
ListNode left = head;//要删除节点的上一个节点
while(right.next!=null && right.next.next!=null){
right = right.next.next;
left = left.next;
}
left.next = left.next.next;//删除中间节点
return head;
}
《左神》38.
题目:给两个整数a,b(a<=b),实现删除链表a/b处节点的函数。若r=0,不删除;其他r的值向上取整,比如r在范围(2/5,3/5]中,取3,删除第三个节点。
思路:1.一次遍历求链表长度;2.求删第几个(向上取整);3.删除r节点
//删除链表a/b处的节点。思路:1.一次遍历求链表长度;2.求删第几个(向上取整);3.删除r节点
public static ListNode removeByRatio(ListNode head, int a, int b){
if(a==0 || b==0 || a==b || head==null){//不删除任何节点
return head;
}
//1.遍历链表,得其长度len
ListNode p = head;
int len = 0;
while(p!=null){
len++;
p = p.next;
}
//2.计算a/b处是第k个节点
int k = (int)Math.ceil( ((double)a/b)*len );
if(k<0){//a/b为负数,不删除节点
return head;
}
//3.删除第k个节点
if(k==1){
head = head.next;
return head;
}
p = head;//从头遍历
while(k!=2){//走到第k-1处
p = p.next;
k--;
}
p.next = p.next.next;//删除第k个节点
return head;
}
《左神》48、leetcode234
题目:判断一个链表是否为回文链表(1\121\1221),要求时间o(n),空间o(1)
思路:由于要求时间o(n),空间o(1),感觉必须要动链表了。1.先找到中间节点,2.然后右半边逆序,3.然后分别从头部和中间开始比较元素是否相等即可。时间o(n),空间o(1)。
//判断一个链表是否为回文结构。思路:1.两个快慢指针先找到中间节点,2.然后右半边链表逆序,3.再从头遍历比较元素是否相等
public boolean isPalindrome(ListNode head) {
if(head==null || head.next==null){
return true;
}
//2.两个快慢指针先找到中间节点
ListNode left = head;
ListNode right = head;
while(right.next!=null && right.next.next!=null){
right = right.next.next;
left = left.next;
}
//2.现将left后边的链表逆序
ListNode cur = left.next;
ListNode temp = cur;
cur = cur.next;
temp.next = null;
while(cur!=null){
temp = cur;
cur = cur.next;
temp.next = left.next;
left.next = temp;
}
//3.从头遍历比较元素是否相等
right = left.next;
left = head;
while(right!=null){
if(left.val!=right.val){//遇到元素不等,不是回文结构,false
return false;
}
left = left.next;
right = right.next;
}
return true;
}
public static void main(String[] args){
ListNode head = new ListNode(1);
head.next = new ListNode(2);
head.next.next = new ListNode(3);
head.next.next.next = new ListNode(3);
head.next.next.next.next = new ListNode(2);
head.next.next.next.next.next = new ListNode(1);
System.out.println(isPalindrome(head));//true
}
《左神》52.
题目:给定一个单向链表的头结点head,节点的值类型是整型,再给定一个整数privot。实现一个调整链表的函数,将链表调整为左部分都是值小于privot的节点,中间部分都是值等于privot的节点,右部分都是大于privot的节点。
例如:链表9-0-4-5-1,pivot=3。
调整后是1-0-4-9-5,也可以是0-1-9-5-4。
思路:解法一:我们可以利用数组额外空间,利用数组partition排序来实现。时间o(n) 空间o(n)
1. 先遍历一遍链表,得到链表长度。
2. 建立一个链表数组。
3. 利用三向快排的划分
4. 调整数组中的next值。
解法二:遍历链表,拆分三个链表small, equal, big表头,最后再连接即可。时间o(n) 空间o(1)
class Node {
public int val;
public Node next;
public Node(int x) {
val = x;
this.next = null;
}
}
public class Solution2 {
//解法一:我们可以利用数组额外空间来实现
//时间o(n) 空间o(n)
//1. 先遍历一遍链表,得到链表长度。
//2. 建立一个链表数组。
//3. 利用三向快排的划分
//4. 调整数组中的next值。
public static Node listPartition1(Node head, int privot){
if(head==null || head.next==null){
return head;
}
//1.遍历链表得到长度。
int len = 0;
Node p = head;
while(p!=null){
len++;
p = p.next;
}
//2.建立一个链表数组。
Node[] arr = new Node[len];
p = head;
for(int i=0; i<len; ++i){
arr[i] = p;
p = p.next;
}
//3.数组partition排序
paitition(arr, 0, len-1, privot);
//4.重新连接数组的node
for(int i=0; i<len-1; ++i){
arr[i].next = arr[i+1];
}
arr[len-1].next = null;
return arr[0];
}
public static void paitition(Node[] arr, int left, int right, int privot){
//注意的这里的partition保持了数组原来的顺序
int small = left;
int big = right;
for(int i=left; i<=right&&i<=big; ++i){
if(arr[i].val<privot){
swap(arr, i, small++);
}
else if(arr[i].val>privot){
swap(arr, i, big--);
}
}
while(big<right){
swap(arr, big++, right--);
}
}
//解法二:遍历链表,拆分三个链表small, equal, big表头,最后再连接即可
//时间o(n) 空间o(1)
public static Node listPartition2(Node head, int privot){
if(head==null || head.next==null){
return head;
}
Node small = new Node(0);
Node smallCopy = small;
Node equal = new Node(0);
Node equalCopy = equal;
Node big = new Node(0);
Node bigCopy = big;
Node p = head;
while(p!=null){//遍历链表
if(p.val<privot){
Node temp = p;
p = p.next;
temp.next = null;
small.next = temp;
small = temp;
}
else if(p.val == privot){
Node temp = p;
p = p.next;
temp.next = null;
equal.next = temp;
equal = temp;
}
else{
Node temp = p;
p = p.next;
temp.next = null;
big.next = temp;
big = temp;
}
}
small.next = equalCopy.next;
equal.next = bigCopy.next;
return smallCopy.next;
}
public static void main(String[] args){
Node head = new Node(9);
Node p = head;
p.next = new Node(0);
p = p.next;
p.next = new Node(3);
p = p.next;
p.next = new Node(4);
p = p.next;
p.next = new Node(5);
p = p.next;
p.next = new Node(1);
show(head);
// head = listPartition1(head, 3);
head = listPartition2(head, 3);
System.out.println();
show(head);
}
public static void show(Node head){
while(head!=null){
System.out.print(head.val + " ");
head = head.next;
}
}
public static void swap(Node[] arr, int i, int j){
Node temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
//out: 9 0 3 4 5 1
0 1 3 9 4 5
leetcode148
题目:Sort a linked list in O(n log n) time using constant space complexity.
思路:归并排序应该是单链表排序最好的方法了,时间o(nlogn),空间o(1)。分而治之,先找到中间节点,拆分成两个链表,merge
public static ListNode sortList(ListNode head) {
if(head==null || head.next==null){
return head;
}
ListNode left = head;
ListNode right = head;
while(right.next!=null && right.next.next!=null){
left = left.next;
right = right.next.next;
}
right = left.next;
left.next = null;//将链表从中间节点拆分,分离成两个链表
left = sortList(head);
right = sortList(right);
return merge(left, right);
}
public static ListNode merge(ListNode l1, ListNode l2){
ListNode guard = new ListNode(0);
ListNode p = guard;
ListNode temp;
while(l1!=null && l2!=null){
if(l1.val<=l2.val){
temp = l1;
l1 = l1.next;
}
else{
temp = l2;
l2 = l2.next;
}
temp.next = null;
p.next = temp;
p = p.next;
}
if(l1!=null){
p.next = l1;
}
if(l2!=null){
p.next = l2;
}
return guard.next;
}
public static void show(ListNode head){
while(head!=null){
System.out.print(head.val + " ");
head = head.next;
}
System.out.println();
}
public static void main(String[] args){
ListNode head = new ListNode(1);
head.next = new ListNode(2);
head.next.next = new ListNode(5);
head.next.next.next = new ListNode(4);
head.next.next.next.next = new ListNode(7);
show(head);
show(sortList(head));
//out:1 2 5 4 7
// 1 2 4 5 7
}
leetcode148
题目:Sort a linked list in O(n log n) time using constant space complexity.
Input: 4->2->1->3
Output: 1->2->3->4
思路:partition思想,用三个指针来控制,pBase指针指向枢纽值结点,pleft指针指向当前最后一个比枢纽值小的结点,pright结点用于遍历,将遇到的比pBase小的结点的值交换到前面去。
//单链表的快排
public ListNode sortList(ListNode head) {
if(head==null || head.next==null){
return head;
}
ListNode tail = head;
while(tail.next!=null){
tail = tail.next;
}
quickSortList(head, tail);
return head;
}
public void quickSortList(ListNode head, ListNode tail){
if(head==tail || head==null || tail==null){
return;
}
ListNode pBase = head; //基准key节点
ListNode pSmall = head; //此节点之前的节点都是比pBase小的
ListNode cur = head.next; //遍历节点
while(cur!=tail.next){
if(cur.val<pBase.val){
pSmall = pSmall.next;
swap(pSmall, cur);
}
cur = cur.next;
}
swap(pBase, pSmall);
quickSortList(head, pSmall);
quickSortList(pSmall.next, tail);
}
public void swap(ListNode a, ListNode b){
int temp = a.val;
a.val = b.val;
b.val = temp;
}
《左神》79、和88题(单链表快排)一起看
题目:单链表的选择排序
思想:时间o(n^2),空间o(1),正常的选择排序思想,每次都从剩下链表头结点遍历,选出剩下链表中最小的节点,接入到排序好的链表部分,这里注意节点的断开与接入的指针变量操作
//单链表的选择排序。时间o(n^2)
public static ListNode selectSortList(ListNode head) {
if(head==null || head.next==null){
return head;
}
ListNode resHeadCopy = new ListNode(0); //哨兵,最终返回resHeadCopy.next
ListNode resHead = resHeadCopy; //存储排序链表的最后一个节点
while(head.next!=null){//选择排序,每次都从剩下链表头结点遍历,选出剩下链表中最小的节点,连接到resHead后
ListNode minTemp = head;
ListNode minPre=null;
ListNode cur = head.next;//遍历剩下链表
ListNode curPre = head;
while(cur!=null){//每次都从剩下链表头结点遍历,找剩下链表中最小节点
if(cur.val<minTemp.val){
minTemp = cur;
minPre = curPre;
}
curPre = cur;
cur = cur.next;
}
ListNode temp = minTemp;//这是找到的min节点
if(temp==head){//如果min节点是头结点
head = head.next;//断开min节点
}
else{
minPre.next = minPre.next.next; //断开min节点
}
temp.next = null;//断开min节点
resHead.next = temp;//min节点连入排序链表尾
resHead = temp;
}
resHead.next = head; //链表剩下最后一个节点接入
return resHeadCopy.next;
}
《左神》59、leetcode 2
题目:You are given two non-empty linked lists representing two non-negative integers. The digits are stored in reverse order and each of their nodes contain a single digit. Add the two numbers and return it as a linked list.
You may assume the two numbers do not contain any leading zero, except the number 0 itself.
Input: (2 -> 4 -> 3) + (5 -> 6 -> 4)
Output: 7 -> 0 -> 8
Explanation: 342 + 465 = 807.
思路:遍历两个链表,每个节点相加放进新建链表即可,有个cnt变量记录进位,注意如果最后还有进位,别忘了再生成一个节点
//两个链表生成相加链表。思路:遍历两个链表,每个节点相加放进新建链表即可,有个cnt变量记录进位
public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
ListNode guard = new ListNode(0);
ListNode p = guard;
int cnt = 0;//进位
int sum;//每两个节点的和
while(l1!=null && l2!=null){
sum = l1.val + l2.val + cnt;
cnt = sum/10;
p.next = new ListNode(sum%10);
p = p.next;
l1 = l1.next;
l2 = l2.next;
}
while(l1!=null){
sum = l1.val + cnt;
cnt = sum/10;
p.next = new ListNode(sum%10);
p = p.next;
l1 = l1.next;
}
while(l2!=null){
sum = l2.val + cnt;
cnt = sum/10;
p.next = new ListNode(sum%10);
p = p.next;
l2 = l2.next;
}
if(cnt!=0){//如果还有进位,则再创建一个节点
p.next = new ListNode(1);
}
return guard.next;
}
leetcode 83
题目:Given a sorted linked list, delete all duplicates such that each element appear only once.
Input: 1->1->2->3->3
Output: 1->2->3
思路:时间o(n),空间o(1),遍历删除即可
public static ListNode deleteDuplicates(ListNode head) {
if(head==null || head.next==null){
return head;
}
ListNode p = head;
ListNode cur = head.next;
while(cur!=null){
if(p.val==cur.val){
p.next = cur.next;
}
else{
p = cur;
}
cur = cur.next;
}
return head;
}
leetcode 82
题目:Given a sorted linked list, delete all nodes that have duplicate numbers, leaving only distinct numbers from the original list.
Example 1:
Input: 1->2->3->3->4->4->5
Output: 1->2->5
思路:定义一个哨兵,定义一个前驱指针和一个现指针,每当前驱指针指向新建的节点,现指针从下一个位置开始往下遍历,遇到相同的则继续往下,直到遇到不同项时,把前驱指针的next指向下面那个不同的元素。如果现指针遍历的第一个元素就不相同,则把前驱指针向下移一位。
代码:
public ListNode deleteDuplicates(ListNode head) {
if(head==null || head.next==null){
return head;
}
ListNode guard = new ListNode(0);//哨兵
guard.next = head;
ListNode pre = guard;//前驱指针
ListNode cur = guard.next;
while(cur!=null){
if(cur.next!=null && cur.next.val==cur.val){//有重复,要准备删除了
int temp = cur.val;//缓存要删除的val
while(cur!=null && cur.val==temp){
cur = cur.next;
}
pre.next = cur;
}
else{
pre = cur;
cur = cur.next;
}
}
return guard.next;
}
《左神》71
题目:Given a sorted linked list, delete all duplicates such that each element appear only once.
Input: 1->2->2->1->3
Output: 1->2->3
思路:哈希set。时间o(n),空间o(n)
public static void deleteDuplicate(ListNode head){
if(head==null || head.next==null){
return;
}
HashSet<Integer> set = new HashSet<>();//哈希表
ListNode p = head;
ListNode cur = head.next;
set.add(p.val);
while(cur!=null){
if(set.contains(cur.val)){
p.next = cur.next;
}
else{
set.add(cur.val);
p = cur;
}
cur = cur.next;
}
}
《左神》82
题目:一个环形单链表从头节点 head 开始不降序,同时由最后的节点指回头节点。给定这样的一个环形单链表的头节点 head 和 一个整数 num ,请生成节点值为 num 的新节点,并插入到这个环形链表中,保证调整后的链表依然有序。
思路:直接从头结点遍历插入即可,时间复杂度为 O(N)、额外空间复杂度为 O(1)。
//环形有序链表中插入新节点
public static ListNode insertNum(ListNode head, int num){
ListNode newNode = new ListNode(num);
if(head==null){
newNode.next = newNode;
return newNode;
}
ListNode cur = head;
if(num <= head.val){//如果要插入到头结点之前
while(cur.next!=head){
cur = cur.next;
}
newNode.next = head;
cur.next = newNode;
head = newNode;
}
else{
while(true){
if(cur.next.val>=num){
newNode.next = cur.next;
cur.next = newNode;
break;
}
cur = cur.next;
}
}
return head;
}
《左神》95、 leetcode545(会员)
题目:给定一颗二叉树的头结点head,按照如下两种标准分别实现二叉树边界节点的逆时针打印。
1.头节点为边界节点
2.叶结点为边界节点
3.如果节点在其所在的层中是最左边或最右边,那么也是边界节点
leetcode 72
题目:Given two words word1 and word2, find the minimum number of operations required to convert word1 to word2.You have the following 3 operations permitted on a word:
Insert a character
Delete a character
Replace a characte
Example 1:
Input: word1 = "horse", word2 = "ros"
Output: 3
Explanation:
horse -> rorse (replace 'h' with 'r')
rorse -> rose (remove 'r')
rose -> ros (remove 'e')
Input: word1 = "intention", word2 = "execution"
Output: 5
Explanation:
intention -> inention (remove 't')
inention -> enention (replace 'i' with 'e')
enention -> exention (replace 'n' with 'x')
exention -> exection (replace 'n' with 'c')
exection -> execution (insert 'u')
思路:DP问题。维护一个二维的数组dp,其中dp[i][j]表示从word1的前i个字符转换到word2的前j个字符所需要的步骤。那我们可以先给这个二维数组dp的第一行第一列赋值,因为第一行和第一列对应的总有一个字符串是空串,于是转换步骤完全是另一个字符串的长度。
转移方程:当word1[i]==word2[j]时,dp[i][j] = dp[i-1][j-1],其他情况时,dp[i][j]是其左,左上,上的三个值中的最小值加1。
if word1[i-1]==word2[j-1]:
dp[i][j] = dp[i - 1][j - 1]
else:
dp[i][j] = min(dp[i-1][j-1], dp[i-1][j], dp[i][j-1]) + 1 //替换、删除、插入
代码:
public int minDistance(String word1, String word2) {
if(word1==null || word2==null){
return 0;
}
int len1 = word1.length();
int len2 = word2.length();
int[][] dp = new int[len1+1][len2+1];
for (int i=0; i<=len2; ++i){//第一行
dp[0][i] = i;
}
for (int i=0; i<=len1; ++i){//第一列
dp[i][0] = i;
}
for(int i=1; i<=len1; ++i){
for (int j=1; j<=len2; ++j){
if(word1.charAt(i-1) == word2.charAt(j-1)){
dp[i][j] = dp[i-1][j-1];
}
else{
dp[i][j] = min(dp[i-1][j-1], dp[i][j-1], dp[i-1][j])+1;
}
}
}
return dp[len1][len2];
}
public int min(int a, int b, int c){
int temp = Math.min(a,b);
return Math.min(temp,c);
}
题目:给定一个不重复的数组,找出所有符合条件的元素:该元素左边都比它小,右边都比它大。e.g. [1,2,3,4,5]–>out:[1,2,3,4,5]所有元素都满足。时间要求o(n)
思路:辅助一个o(n)数组temp[],遍历两次数组即可。第一次从后往前记录temp[],temp[i]表示从结尾到i时最小的数,第二次遍历从前往后,tempMax存从头到i时最大的数,如果nums[i]比tempMax大且比temp[i]小则nums[i]符合条件。
//找出数组中左边比他小右边比他大的所有元素。思路:辅助o(n)的temp[],遍历两遍数组即可
public static ArrayList<Integer> findEleBigLeftSmallRight(int[] numbers){
ArrayList<Integer> res = new ArrayList<>();
if(numbers==null || numbers.length==0){
return res;
}
int len = numbers.length;
if(len==1){//只有一个节点时直接返回该节点,无需比较
res.add(numbers[0]);
return res;
}
//1.从后往前遍历,存最小的数
int[] temp = new int[len];//辅助数组
temp[len-1] = numbers[len-1];
for(int i=len-2; i>=0; --i){
temp[i] = numbers[i]<temp[i+1] ? numbers[i] : temp[i+1];
}
//2.从前往后遍历,找符合条件的数
int tempMax = Integer.MIN_VALUE;//存最大的数
for(int i=0; i<len-1; ++i){
if(numbers[i]>tempMax && numbers[i]<temp[i+1]){
res.add(numbers[i]);//符合条件
}
if(numbers[i]>tempMax){
tempMax = numbers[i];
}
}
if(numbers[len-1]>tempMax){//最后一个数
res.add(numbers[len-1]);
}
return res;
}
leetcode 98
题目:判断二叉树为BST(二叉搜索树)
思路:中序遍历判断且使用全局变量记录前继节点的值
代码:
private Long last = Long.MIN_VALUE;//全局变量记录前继节点的值
//中序遍历判断
public boolean isValidBST(TreeNode root) {
if(root==null){
return true;
}
if(!isValidBST(root.left)){
return false;
}
if(root.val<=last){
return false;
}
last = Long.valueOf(root.val);
return isValidBST(root.right);
}
leetcode 648
题目:In English, we have a concept called root, which can be followed by some other words to form another longer word - let’s call this word successor. For example, the root an, followed by other, which can form another word another.
Now, given a dictionary consisting of many roots and a sentence. You need to replace all the successor in the sentence with the root forming it. If a successor has many roots can form it, replace it with the root with the shortest length.
You need to output the sentence after the replacement.
Example 1:
Input: dict = ["cat", "bat", "rat"]
sentence = "the cattle was rattled by the battery"
Output: "the cat was rat by the bat"
Note:
The input will only have lower-case letters.
1 <= dict words number <= 1000
1 <= sentence words number <= 1000
1 <= root length <= 100
1 <= sentence words length <= 1000
题目大意:给定一个字典,里面是词根root,给定一句话,如果这句话的单词前缀是字典中词根,则将单词用词根替换,如果有多个词根,找最短的词根替换,最后返回替换好的话
思路1(不可取):暴力替换。将这句话split成words[]单词组,将每个单词从前往后字符组的依次与hash字典中的词根比较,如果出现就替换。时间o(nk),n这句话,k字典,空间辅助hash o(k)。时间效率很低
public String replaceWords(List<String> dict, String sentence) {
HashSet<String> set = new HashSet<>(dict);
String[] words = sentence.split(" ");
StringBuilder sb = new StringBuilder();
for(int i=0; i<words.length; ++i){
for(int j=1; j<words[i].length(); ++j){
String root = words[i].substring(0,j);
if(set.contains(root)){
words[i] = root;
break;
}
}
sb.append(words[i] + " ");
}
return sb.toString().trim();
}
思路2(可取,但还是不够好):不暴力替换,采用了字典树的思想。1.字典的roots词根用首字母索引,首字母相同的前缀都放到同一个HashSet中,总共需要26个索引数组;2.将这句话split成words[]单词组,遍历words单词数组,一个单词一个单词的在字典树中替换,最后返回替换好的话。时间o(n),空间o(k),n是原句子,k是字典
//思路:字典树思想。将字典中的词根按照首字母构建字典
public String replaceWords(List<String> dict, String sentence) {
HashSet[] set = new HashSet[26];//字典树,用首字母索引,首字母相同的前缀都放到同一个HashSet中,总共需要26个索引数组
String[] words = sentence.split(" ");
StringBuilder sb = new StringBuilder();
//1.构建字典树,26个字母开头索引
for(int i=0; i<dict.size(); ++i){
String word = dict.get(i);
int wordIndex = word.charAt(0)-'a';
if(set[wordIndex]==null){
set[wordIndex] = new HashSet<String>();
}
set[wordIndex].add(word);
}
//2.遍历words单词数组,一个单词一个单词的在字典树中替换
for(int i=0; i<words.length; ++i){
int wordIndex = words[i].charAt(0)-'a';
if(set[wordIndex]==null){//这个单词在字典树中没有首字母索引,即也没有词根对应,continue
sb.append(words[i] + " ");
continue;
}
Iterator<String> iterator = set[wordIndex].iterator();
while(iterator.hasNext()) {
String root = iterator.next();
if (words[i].startsWith(root) && root.length() < words[i].length()) {//找最短的词根替换
words[i] = root;
}
}
sb.append(words[i] + " ");//替换好的单词加入到新话中
}
return sb.toString().trim();
}
思路3(高效字典树,棒):思路2只是很简单用首字母进行索引实现字典树,但还是不够,应该使用字典树(也叫前缀树)实现高效查询。
//字典树节点
class TrieNode{
char c;
TrieNode[] children = new TrieNode[26];
boolean isComplete = false;
public TrieNode(char c){
this.c = c;
}
}
class Solution {
//思路:构建字典树。
public String replaceWords(List<String> dict, String sentence) {
if(sentence==null || sentence.length()<2 || dict==null || dict.size()==0){
return sentence;
}
//1.遍历字典中的词根,构建字典树
TrieNode root = new TrieNode('c');//字典树的根节点
for(String dictWord: dict){//遍历字典中的词根
char[] chs = dictWord.toCharArray();
TrieNode temp = root;
for(char c: chs){//对每个词根进行字典树构建
if(temp.children[c-'a']==null){
temp.children[c-'a'] = new TrieNode(c);
}
temp = temp.children[c-'a'];
}
temp.isComplete = true;//一个词根构建完,标记位置为true
}
//2.遍历原话的words[]单词数组,对每个单词进行字典树替换
StringBuilder sb = new StringBuilder();
String[] words = sentence.split(" ");
for(int i=0; i<words.length; ++i){//遍历原单词数组words[]
TrieNode temp = root;
StringBuilder tempSb = new StringBuilder();
boolean can_change = false;//是否找到一个可以替换的词根
for(char ch: words[i].toCharArray()){
if(temp.children[ch-'a']==null){//当前字符在字典树中没有索引
break;
}
tempSb.append(ch);
temp = temp.children[ch-'a'];
if(temp.isComplete==true){//找到了当前单词对应的一个词根
can_change = true;
break;
}
}
if(can_change){//将单词替换成词根
sb.append(tempSb.toString() + " ");
}
else{//没有对应的词根,单词不变
sb.append(words[i] + " ");
}
}
return sb.toString().trim();
}
}
参考:LeoYang
Coding and learning
BFS和DFS详解以及java实现
总的来说,BFS多用于寻找最短路径的问题,DFS多用于快速发现底部节点。
BFS主要思想是从起始点开始,将其邻近的所有顶点都加到一个队列中去,然后标记下这些顶点离起始顶点的距离为1.最后将起始顶点标记为已访问,今后就不会再访问。然后再从队列中取出最先进队的顶点A,也取出其周边邻近节点,加入队列末尾,将这些顶点的距离相对A再加1,最后离开这个顶点A。依次下去,直到队列为空为止。从上面描述的过程我们知道每个顶点被访问的次数最多一次(已访问的节点不会再访问),而对于连通图来说,每个顶点都会被访问。加上每个顶点的邻接链表都会被遍历,因此BFS的时间复杂度是O(V+E),其中V是顶点个数,E是边数,也就是所有邻接表中的元素个数。
DFS深度优先搜索是从起始顶点开始,递归访问其所有邻近节点,比如A节点是其第一个邻近节点,而B节点又是A的一个邻近节点,则DFS访问A节点后再访问B节点,如果B节点有未访问的邻近节点的话将继续访问其邻近节点,否则继续访问A的未访问邻近节点,当所有从A节点出去的路径都访问完之后,继续递归访问除A以外未被访问的邻近节点。
leetcode 542
题目:Given a matrix consists of 0 and 1, find the distance of the nearest 0 for each cell. The distance between two adjacent cells is 1.
Example 1:
Input:
0 0 0
0 1 0
0 0 0
Output:
0 0 0
0 1 0
0 0 0
Example 2:
Input:
0 0 0
0 1 0
1 1 1
Output:
0 0 0
0 1 0
1 2 1
Note:
The number of elements of the given matrix will not exceed 10,000.
There are at least one 0 in the given matrix.
The cells are adjacent in only four directions: up, down, left and right.
思路:BFS。这道题给了我们一个只有0和1的矩阵,让我们求每一个1到离其最近的0的距离,其实也就是求一个距离场,而求距离场那么BFS将是不二之选。
1.首先遍历一次矩阵,将值为0的点都存入queue,将值为1的点换成MAX_VALUE;
2.对队列中的每一个0进行BFS上下左右搜索:从queue中取出一个数字,遍历其周围四个点,如果越界或者周围点的值小于等于当前值加1,则直接跳过;否则将周围点的值更新为当前值加1,然后把周围点的坐标加入queue
代码:
public int[][] updateMatrix(int[][] matrix) {
if(matrix==null || matrix.length==0 || matrix[0].length==0){
return matrix;
}
int m = matrix.length;
int n = matrix[0].length;
//1.把原矩阵中1换成MAX_VALUE; 2.把0全部放进队列
Queue<Pair<Integer,Integer> > queue = new LinkedList<>();
for(int i=0; i<m; ++i){
for (int j=0; j<n; ++j){
if(matrix[i][j]==0){
queue.offer(new Pair<>(i, j));
}
else{
matrix[i][j] = Integer.MAX_VALUE;
}
}
}
//3.对队列中的每一个0进行BFS上下左右搜索
int[][] dirs = new int[][]{{1,0}, {-1,0}, {0,-1}, {0,1}}; //上下左右
while(!queue.isEmpty()){
Pair<Integer, Integer> origin = queue.poll();
for(int i=0; i<4; ++i){
int x = origin.getKey() + dirs[i][0];
int y = origin.getValue() + dirs[i][1];
if(x<0 || x>=m || y<0 || y>=n || matrix[x][y]<=( matrix[origin.getKey()][origin.getValue()]+1 ) ){
continue;
}
matrix[x][y] = matrix[origin.getKey()][origin.getValue()]+1;
queue.offer(new Pair<>(x, y));//BFS入队别忘了
}
}
return matrix;
}
leetcode 200
题目:Given a 2d grid map of '1’s (land) and '0’s (water), count the number of islands. An island is surrounded by water and is formed by connecting adjacent lands horizontally or vertically. You may assume all four edges of the grid are all surrounded by water.
Example 1:
Input:
11110
11010
11000
00000
Output: 1
思路:找岛屿的个数。思路:dfs,找到图中值为1的点,分别从上、下、左、右四个方向搜索。搜索过的‘1’就置为‘0’,每完成一整片陆地的搜索,计数器加1
代码:
public int numIslands(char[][] grid) {
if(grid==null || grid.length==0 || grid[0].length==0){
return 0;
}
int res = 0;
int m = grid.length;
int n = grid[0].length;
for(int i=0; i<m; ++i){
for(int j=0; j<n; ++j){
if(grid[i][j]=='1'){//找到一个岛屿
dfs(grid, m, n, i, j);//对该岛屿进行dfs搜索土地,并将岛屿的全部土地变为'0'已访问
res++;
}
}
}
return res;
}
public void dfs(char[][] grid, int m, int n, int i, int j){
if(i<0 || i>=m || j<0 || j>=n || grid[i][j]=='0'){
return;
}
grid[i][j] = '0';//标记为已访问
dfs(grid, m , n, i-1, j);
dfs(grid, m , n, i+1, j);
dfs(grid, m , n, i, j-1);
dfs(grid, m , n, i, j+1);//上下左右
}
leetcode 695
题目:Given a non-empty 2D array grid of 0’s and 1’s, an island is a group of 1’s (representing land) connected 4-directionally (horizontal or vertical.) You may assume all four edges of the grid are surrounded by water.
Find the maximum area of an island in the given 2D array. (If there is no island, the maximum area is 0.)
Example 1:
[[0,0,1,0,0,0,0,1,0,0,0,0,0],
[0,0,0,0,0,0,0,1,1,1,0,0,0],
[0,1,1,0,1,0,0,0,0,0,0,0,0],
[0,1,0,0,1,1,0,0,1,0,1,0,0],
[0,1,0,0,1,1,0,0,1,1,1,0,0],
[0,0,0,0,0,0,0,0,0,0,1,0,0],
[0,0,0,0,0,0,0,1,1,1,0,0,0],
[0,0,0,0,0,0,0,1,1,0,0,0,0]]
Given the above grid, return 6. Note the answer is not 11, because the island must be connected 4-directionally.(斜着的不算)
思路:在很多岛屿中,返回最大岛屿面积。思路:dfs,找到图中值为1的点,分别从上、下、左、右四个方向搜索。搜索过的‘1’就置为‘0’,每完成一整片陆地的搜索并计算面积。
代码:
public int maxAreaOfIsland(int[][] grid) {
if(grid==null || grid.length==0 || grid[0].length==0){
return 0;
}
int resMax = 0;
int m = grid.length;
int n = grid[0].length;
for(int i=0; i<m; ++i){
for(int j=0; j<n; ++j){
if(grid[i][j]==1){//找到一个岛屿
int[] tempMax = new int[]{0};//计算当前岛屿的面积
dfs(grid, m, n, i, j, tempMax);//对该岛屿进行dfs搜索土地并计算面积,并将岛屿的全部土地变为'0'已访问
resMax = Math.max(resMax, tempMax[0]);
}
}
}
return resMax;
}
public void dfs(int[][] grid, int m, int n, int i, int j, int[] tempMax){
if(i<0 || i>=m || j<0 || j>=n || grid[i][j]==0){
return;
}
grid[i][j] = 0;//标记为已访问
tempMax[0]++;
dfs(grid, m , n, i-1, j, tempMax);
dfs(grid, m , n, i+1, j, tempMax);
dfs(grid, m , n, i, j-1, tempMax);
dfs(grid, m , n, i, j+1, tempMax);//上下左右
}
leetcode 463
题目大意:二维地图,每个单元格的长度为1的方形。1代表陆地,0代表水,上、下、左、右四个方向的单元格相连,求出相连陆地单元格的周长。
思路:图中只有唯一一个岛屿,不用dfs,直接遍历图中所有的节点,对是岛屿的每一个节点计算其贡献的周长,加起来即可。
代码:
//求图中唯一岛屿的周长。思路://遍历图中所有的节点,对是岛屿的每一个节点计算其贡献的周长,加起来即可
//对岛屿的一个节点计算周长的规律:周围相邻有0/1/2/3/4的节点的话,对应的周长分别为4/3/3/2/1/0
public int islandPerimeter(int[][] grid) {
if(grid==null || grid.length==0 || grid[0].length==0){
return 0;
}
int resPrm = 0;//该岛屿的周长
int m = grid.length;
int n = grid[0].length;
for(int i=0; i<m; ++i){//遍历图中所有的节点,对是岛屿的每一个节点计算其贡献的周长,加起来即可
for(int j=0; j<n; ++j){
if(grid[i][j]==1){
int temp = 4;
if(i-1>=0 && grid[i-1][j]==1){//上:有相邻的一个节点
temp--;
}
if(i+1<m && grid[i+1][j]==1){//下:有相邻的一个节点
temp--;
}
if(j-1>=0 && grid[i][j-1]==1){//左:有相邻的一个节点
temp--;
}
if(j+1<n && grid[i][j+1]==1){//右:有相邻的一个节点
temp--;
}
resPrm += temp;
}
}
}
return resPrm;
}
leetcode 130
题目:这道题的意思是将所有被X包围的O都变为X(边缘的不算)
思路:1.从图的4个边界出发DFS找O的土地,全部换成*;2.遍历图中剩下的节点,将O换成X,将*换成O
代码:
public void solve(char[][] board) {
if(board==null || board.length==0 || board[0].length==0){
return;
}
int m = board.length;
int n = board[0].length;
//1.从图的4个边界出发DFS找O的土地,全部换成*
for (int i = 0; i < n; ++i){
dfsSolve(board, m, n, 0, i);//上边界
dfsSolve(board, m, n, m-1, i);//下边界
}
for (int i = 0; i < m; ++i){
dfsSolve(board, m, n, i, 0);//左边界
dfsSolve(board, m, n, i, n-1);//右边界
}
//2.遍历图中剩下的节点,将O换成X,将*换成O
for (int i = 0; i < m; ++i) {
for (int j = 0; j < n; ++j) {
if(board[i][j]=='*'){
board[i][j]='O';
}
else if(board[i][j]=='O'){
board[i][j]='X';
}
}
}
}
//从图的4个边界出发DFS找O的土地,访问过的换成*
public void dfsSolve(char[][] grid, int m, int n, int i, int j){
if(i<0 || i>=m || j<0 || j>=n || grid[i][j]=='X' || grid[i][j]=='*'){
return;
}
grid[i][j] = '*';//把边界O换成*
dfsSolve(grid, m, n, i+1, j);//上
dfsSolve(grid, m, n, i-1, j);//下
dfsSolve(grid, m, n, i, j-1);//左
dfsSolve(grid, m, n, i, j+1);//右
}
1.单源最短路径_1.Dijkstra算法
2.单源最短路径_2.Bellman-Ford算法
3.单源最短路径_3.SPFA算法
4.多源最短路径_Floyd-Warshall算法
普通实现的时间复杂度为O(V2),若基于 Fibonacci heap 的最小优先队列实现版本则时间复杂度为 O(E+VlogV)
Dijkstra算法采用的是一种贪心的策略,声明一个数组dis来保存源点到各个顶点的最短距离和一个保存已经找到了最短路径的顶点的集合:T,初始时,原点 s 的路径权重被赋为 0 (dis[s] = 0)。若对于顶点 s 存在能直接到达的边(s,m),则把dis[m]设为w(s, m),同时把所有其他(s不能直接到达的)顶点的路径长度设为无穷大。初始时,集合T只有顶点s
然后,从dis数组选择最小值,则该值就是源点s到该值对应的顶点的最短路径,并且把该点加入到T中,此时完成一个顶点, 然后,我们需要看看新加入的顶点是否可以到达其他顶点并且看看通过该顶点到达其他点的路径长度是否比源点直接到达短,如果是,那么就替换这些顶点在dis中的值。 然后,又从dis中找出最小值,重复上述动作,直到T中包含了图的所有顶点。
图片截图:
自己跑过的代码:
/**
* @FileName: Dijkstra
* @Author: braincao
* @Date: 2019/1/9 16:55
* @Description: Dijkstra最短路径算法实现
*/
public class Dijkstra {
private static int N = Integer.MAX_VALUE - 2;
private static int[][] Graph = {
{0, 1, 5, N, N, N, N, N, N},
{1, 0, 3, 7, 5, N, N, N, N},
{5, 3, 0, N, 1, 7, N, N, N},
{N, 7, N, 0, 2, N, 3, N, N},
{N, 5, 1, 2, 0, 3, 6, 9, N},
{N, N, 7, N, 3, 0, N, 5, N},
{N, N, N, 3, 6, N, 0, 2, 7},
{N, N, N, N, 9, 5, 2, 0, 4},
{N, N, N, N, N, N, 7, 4, 0}};
public static void main(String[] args) {
dijkstra(0, Graph);
}
/**
* Dijkstra最短路径。
* 即图中"节点vs"到其它各个节点的最短路径。
*
* @param vs 起始节点
* @param Graph 图
*/
public static void dijkstra(int vs, int[][] Graph) {
int NUM = Graph.length;//图中节点的个数
int[] prenode = new int[NUM];//前驱节点数组
int[] mindist = new int[NUM];// 最短距离数组
boolean[] find = new boolean[NUM];// 该节点是否已经找到最短路径,即已经确定节点的集合,初始里面只有vs节点
int vnear = 0;
for (int i = 0; i < NUM; i++) {//初始化
prenode[i] = i;
mindist[i] = Graph[vs][i];
find[i] = false;
}
find[vs] = true;//节点自己到自己的最短路径能找到
for (int v = 1; v < NUM; v++) {//循环NUM-1次
// 每次循环找一个距离vs最近的节点vnear和最短距离min
int min = Integer.MAX_VALUE;
for (int j = 0; j < NUM; j++) {
if (!find[j] && mindist[j] < min) {
min = mindist[j];
vnear = j;
}
}
find[vnear] = true;//vnear节点已经确定,访问标记
// 根据vnear修正vs到其他所有节点的前驱节点及距离,即松弛操作
for (int k = 0; k < NUM; k++) {
if (!find[k] && (min + Graph[vnear][k]) < mindist[k]) {
prenode[k] = vnear;
mindist[k] = min + Graph[vnear][k];
}
}
}
for (int i = 0; i < NUM; i++) {
System.out.println("v" + vs + "...v" + prenode[i] + "->v" + i + ", s=" + mindist[i]);
}
}
}
out:
v0...v0->v0, s=0
v0...v1->v1, s=1
v0...v1->v2, s=4
v0...v2->v3, s=-2147483647
v0...v3->v4, s=-2147483645
v0...v7->v5, s=-2147483642
v0...v2->v6, s=-2147483647
v0...v2->v7, s=-2147483647
v0...v2->v8, s=-2147483647
时间o(VE),比dijkstra慢
Bellman-Ford 算法描述:
1.创建源顶点 v 到图中所有顶点的距离的集合 distSet,为图中的所有顶点指定一个距离值,初始均为 Infinite,源顶点距离为 0;
计算最短路径,执行 V - 1 次遍历,每次遍历中,依赖所有边进行松弛操作;
2.松弛操作:对于图中的每条边:如果起点 u 的距离 d 加上边的权值 w 小于终点 v 的距离 d,则更新终点 v 的距离值 d;
3.检测图中是否有负权边形成了环,遍历图中的所有边,计算 u 至 v 的距离,如果对于 v 存在更小的距离,则说明存在环;
自己跑过的代码:
import java.util.HashSet;
import java.util.Set;
/**
* @FileName: Dijkstra
* @Author: braincao
* @Date: 2019/1/9 16:55
* @Description: Bellman-Ford最短路径算法实现
*/
class Edge
{
int start; //有向边的起点
int end; //有向边的终点
int weight;//边的权重
}
public class Bellman {
private final static int N = 9999;//不可达边的权值
/**
* @param: 图结构、源节点的下标(0~节点个数-1)
* @return
*/
public void Bellman_Ford(int[][] graph, int source){
//图结构中的节点数目、边集合
int nodenum = graph.length;
Set<Edge> edge = new HashSet<>();//边集合
//1.根据graph的图结构给edge[]边数组赋值
for(int i=0; i<nodenum; ++i){
for(int j=i+1; j<nodenum; ++j){
if(graph[i][j]!=N && graph[i][j]!=0){//有边的话就赋值,最终有edgeIndex条边
Edge tempEdge = new Edge();
tempEdge.start = i;
tempEdge.end = j;
tempEdge.weight = graph[i][j];
edge.add(tempEdge);
}
}
}
//2.为dist最短路径数组初始化赋值
int[] dist = new int[nodenum];
for(int i=0; i<nodenum; i++){
dist[i]=graph[source][i];
}
dist[source]=0;
//3.循环nodenum-1次,每次都遍历所有边,进行松弛操作
for(int i=0; i<nodenum-1; i++)//循环nodenum-1次
{
for (Edge edgeTemp: edge){//每次都遍历所有边,进行松弛操作
int start = edgeTemp.start;
int end = edgeTemp.end;
int weight = edgeTemp.weight;
if(dist[end]>dist[start]+weight){//松弛操作
dist[end]=dist[start]+weight;
}
}
}
//4.判断是否存在负回路
boolean flag=false;//是否存在回路
for (Edge edgeTemp: edge){
if( dist[edgeTemp.end] > dist[edgeTemp.start]+edgeTemp.weight )
{
flag=true;
break;
}
}
if(!flag){//不存在负回路的话打印dist最短路径
for(int i=0;i<nodenum;i++)
System.out.println(dist[i]);//打印源节点到每个节点的距离
}
}
public static void main(String[] args){
Bellman bellman = new Bellman();
int[][] graph = new int[][]{
{0, 1, 5, N},
{1, 0, 3, 7},
{5, 3, 0, N},
{N, 7, N, 0}};
bellman.Bellman_Ford(graph, 2);//节点0、1、2、3,现求节点2的单源最短路径
}
}
SPFA(Shortest Path Faster Algorithm)算法是求单源最短路径的一种算法,它是Bellman-ford的队列优化,它是一种十分高效的最短路算法。
很多时候,给定的图存在负权边,这时类似Dijkstra等算法便没有了用武之地,而Bellman-Ford算法的复杂度又过高,SPFA算法便派上用场了。SPFA的复杂度大约是O(kE),k是每个点的平均进队次数(一般的,k是一个常数,在稀疏图中小于2)。
但是,SPFA算法稳定性较差,在稠密图中SPFA算法时间复杂度会退化。
实现方法:建立一个队列,初始时队列里只有起始点,在建立一个表格记录起始点到所有点的最短路径(该表格的初始值要赋为极大值,该点到他本身的路径赋为0)。然后执行松弛操作,用队列里有的点去刷新起始点到所有点的最短路,如果刷新成功且被刷新点不在队列中则把该点加入到队列最后。重复执行直到队列为空。
此外,SPFA算法还可以判断图中是否有负权环,即一个点入队次数超过N。
图结构为邻接矩阵的spfa代码:
import java.util.*;
/**
* @FileName: SPFA
* @Author: braincao
* @Date: 2019/1/9 16:55
* @Description: SPFA最短路径算法实现
*/
public class SPFA {
private final static int N = 9999;//不可达边的权值
/**
* @param: 图结构(邻接矩阵)、源节点的下标(0~节点个数-1)
* @return
*/
public static int[] spfa(int[][] graph, int source){
int nodenum = graph.length;
int[] dist = new int[nodenum];//最短路径数组
boolean[] used = new boolean[nodenum];//访问标记位
for(int i = 0; i<nodenum; i++){
dist[i] = Integer.MAX_VALUE;
used[i] = false;
}
int[] num = new int[nodenum]; //记录每个节点遍历过的次数,用于检测负向环
Queue<Integer> queue = new LinkedList<>();//辅助队列
int path[] = new int[nodenum]; //记录最短路的路径
dist[source] = 0; //source顶点到自身距离为0
used[source] = true; //表示source顶点进入数组队
num[source] = 1; //表示source顶点已被遍历一次
queue.add(source); //source顶点入队
boolean flag = false;//是否存在回路
while(!queue.isEmpty()) {
int u = queue.poll(); //获取队头
used[u] = false;
for (int v = 0; v < nodenum; ++v) {//对每一个u的相邻节点进行松弛操作
if (graph[u][v] != N) {u与v直接邻接
if (dist[v] > dist[u] + graph[u][v]) {
dist[v] = dist[u] + graph[u][v];
path[v] = u;
if (!used[v]) {//如果v没有访问过,入队
queue.offer(v);
used[v] = true;
num[v]++;
if (num[v] >= nodenum) {//遍历次数大于等于节点数,存在负向环,break
flag = true;
break;
}
}
}
}
}
if(flag){//存在负向环,break
break;
}
}
if(!flag){//不存在负回路的话打印dist最短路径
for(int i=0;i<nodenum;i++)
System.out.println(dist[i]);//打印源节点到每个节点的距离
}
return dist;
}
public static void main(String[] args){
int[][] graph = new int[][]{
{0, 1, 5, N},
{1, 0, 3, 7},
{5, 3, 0, N},
{N, 7, N, 0}};
int[] dist = spfa(graph, 2);//节点0、1、2、3,现求节点2的单源最短路径
System.out.println(Arrays.toString(dist));//out:[4, 3, 0, 10]
}
}
图结构为邻接链表的spfa代码:
import java.util.*;
/**
* @FileName: SPFA
* @Author: braincao
* @Date: 2019/1/9 16:55
* @Description: SPFA最短路径算法实现
*/
public class SPFA {
private final static int N = 9999;//不可达边的权值
/**
* @param: 图结构(邻接矩阵)、源节点的下标(0~节点个数-1)
* @return 单源最短路径
*/
public static int[] spfa(int[][] graph, int source){
//1.邻接矩阵转成邻接表
int nodenum = graph.length;
List[] vex = new List[nodenum];
for(int i=0; i<nodenum; ++i){
vex[i] = new ArrayList();
}
for(int i=0; i<nodenum; ++i){
for(int j=i+1; j<nodenum; ++j){//假设给定的图是无向图
if(graph[i][j]!=N){
vex[i].add(j);
vex[j].add(i);
}
}
}
//2.spfa
int[] dist = new int[nodenum];//最短路径数组,初始化为MAX_VALUE
for(int i=0; i<nodenum; ++i){
dist[i] = Integer.MAX_VALUE;
}
boolean[] used = new boolean[nodenum];//访问标记位
Queue<Integer> queue = new LinkedList<>();//辅助队列
int[] num = new int[nodenum];//记录每个节点遍历过的次数,用于检测负向环
queue.offer(source);//source顶点入队
dist[source] = 0;//source顶点到自身距离为0
num[source] = 1;//表示source顶点已被遍历一次
used[source] = true;
boolean flag = false;//是否存在负向环
int[] path = new int[nodenum];//记录最短路径,初始化上一个节点都为source
for(int i=0; i<nodenum; ++i){
path[i] = source;
}
while (!queue.isEmpty()){
int u = queue.poll();//获取队头
used[u] = false;
for(int i=0; i<vex[u].size(); ++i){//对u的每个相邻节点v进行松弛操作
int v = (int)vex[u].get(i);
if( dist[v]>dist[u]+graph[u][v] ){
dist[v] = dist[u]+graph[u][v];
path[v] = u;
if(!used[v]){//如果v没有访问过,入队
queue.offer(v);
num[v]++;
used[v] = true;
if(num[v]>=nodenum){//遍历次数大于等于节点数,存在负向环,break
flag = true;
break;
}
}
}
}
if(flag){//存在负向环,break
break;
}
}
if(!flag){//不存在负回路的话打印dist最短路径
for(int i=0;i<nodenum;i++){
System.out.println("source:"+source+"...pass:"+path[i]+"-->dest:"+i+",distance:"+dist[i]);
}
}
return dist;
}
public static void main(String[] args){
int[][] graph = new int[][]{
{0, 1, 5, N},
{1, 0, 3, 7},
{5, 3, 0, N},
{N, 7, N, 0}};
int[] dist = spfa(graph, 2);//节点0、1、2、3,现求节点2的单源最短路径
System.out.println(Arrays.toString(dist));
//out:
//source:2...pass:1-->dest:0,distance:4
//source:2...pass:2-->dest:1,distance:3
//source:2...pass:2-->dest:2,distance:0
//source:2...pass:1-->dest:3,distance:10
//[4, 3, 0, 10]
}
}
时间o(V^3)
最开始只允许经过1号顶点进行中转,接下来只允许经过1和2号顶点进行中转……允许经过1~n号所有顶点进行中转,求任意两点之间的最短路程。用一句话概括就是:从i号顶点到j号顶点只经过前k号点的最短路程。
核心代码:
for(k=1;k<=n;k++)
for(i=1;i<=n;i++)
for(j=1;j<=n;j++)
if(e[i][j]>e[i][k]+e[k][j])
e[i][j]=e[i][k]+e[k][j];
Word Ladder--leetcode 127
Word Ladder2--leetcode 126
leetcode 127
题目:Given two words (beginWord and endWord), and a dictionary’s word list, find the length of shortest transformation sequence from beginWord to endWord, such that:
1.Only one letter can be changed at a time.
2.Each transformed word must exist in the word list. Note that beginWord is not a transformed word.
Note:
Return 0 if there is no such transformation sequence.
All words have the same length.
All words contain only lowercase alphabetic characters.
You may assume no duplicates in the word list.
You may assume beginWord and endWord are non-empty and are not the same.
Example 1:
Input:
beginWord = "hit",
endWord = "cog",
wordList = ["hot","dot","dog","lot","log","cog"]
Output: 5
Explanation: As one shortest transformation is "hit" -> "hot" -> "dot" -> "dog" -> "cog",
return its length 5.
题目大意:
思路:最短路径问题。1.构造图结构(邻接表); 2.用SPFA最短路算法求beginWord到endWord的最短改变次数
代码:
//Word Ladder找出beginWord到endWord的最小改变次数。
//思路:最短路问题。1.构图; 2.用SPFA最短路算法求beginWord到endWord的最短改变次数
public int ladderLength(String beginWord, String endWord, List<String> wordList) {
if(diff(beginWord, endWord)==1){
return 2;
}
//1.构造图的邻接表
int size = wordList.size();
List[] edge = new List[size+2];//size装beginWord,size+1装endWord
for(int i=0; i<edge.length; ++i){
edge[i] = new ArrayList<Integer>();
}
boolean can_arrive = false;//从beginWord到endWord是否能顺利改变
for(int i=0; i<size; ++i){
for(int j=i+1; j<size; ++j){
if(diff(wordList.get(i), wordList.get(j)) == 1){//这两个单词可以转变,图中有边
edge[i].add(j);
edge[j].add(i);
}
}
if(diff(beginWord, wordList.get(i))==1){//字典中的当前单词与beginWord可以改变,图中有边
edge[size].add(i);
}
int temp = diff(endWord, wordList.get(i));
if(temp == 1){//字典中的当前单词与endWord可以改变,图中有边
edge[i].add(size+1);
}
if(temp == 0){//字典中包含endWord,can_arrive
can_arrive = true;
}
}
if(can_arrive==false){
return 0;
}
//2.上述构好图后,用SPFA最短路算法求beginWord到endWord的最短路径
int[] dist = new int[size+2];//求edge[size]节点的单源最短路径数组
for(int i = 0; i<size+2; i++){
dist[i] = Integer.MAX_VALUE;
}
dist[size] = 1;
Queue<Integer> queue = new LinkedList<>();
queue.offer(size);//起始节点入队
while(!queue.isEmpty()) {
int queFirstNode = queue.poll(); //获取队头
int dest = dist[queFirstNode];
for(int i=0; i<edge[queFirstNode].size(); ++i){//遍历queFirstNode为起点的每条边
//当队头的节点等于边edgeTemp的起点时,进行松弛操作
int temp = (int)edge[queFirstNode].get(i);
if( dist[temp] > dest+1 ) {
dist[temp] = dest+1;
queue.add(temp);
if(temp == size+1) {//该节点是终点,即改变成功
return dist[temp];//从起点(size)到终点(size+1)的最短距离
}
}
}
}
return 0;
}
//判断两个字符串不同的字符个数
public int diff(String word1, String word2){
int res = 0;
for(int i=0; i<word1.length(); ++i){
if(word1.charAt(i)!=word2.charAt(i)){
res++;
}
}
return res;
}
leetcode 126
题目:题目与上面同,只是结果应该返回所有转换的路径而不是最短的路径长度
思路:还是构造邻接链表+SPFA最短路径算法,当找到终点时用dfs从终点节点反推找出所有不同的最短路径即可。
Example 1:
Input:
beginWord = "hit",
endWord = "cog",
wordList = ["hot","dot","dog","lot","log","cog"]
Output:
[
["hit","hot","dot","dog","cog"],
["hit","hot","lot","log","cog"]
]
代码:
import java.util.*;
public class Solution {
List<List<String>> res = new ArrayList<>();
int size;
int[] dist;
List[] edge;
String a;
String b;//这些变量弄成全局是为了dfs函数中使用
List<String> wl;
ArrayList<String> tempPath = new ArrayList<>();
public List<List<String>> findLadders(String beginWord, String endWord, List<String> wordList) {
a = beginWord;
b = endWord;
wl = wordList;
size = wordList.size();
//1.构造图的邻接表
edge = new List[size+2];//size装beginWord,size+1装endWord
for(int i=0; i<edge.length; ++i){
edge[i] = new ArrayList<Integer>();
}
boolean can_arrive = false;//从beginWord到endWord是否能顺利改变
if(diff(beginWord, endWord)==1){
edge[size].add(size+1);
edge[size+1].add(size);
}
for(int i=0; i<size; ++i){
for(int j=i+1; j<size; ++j){
if(diff(wordList.get(i), wordList.get(j)) == 1){//这两个单词可以转变,图中有边
edge[i].add(j);
edge[j].add(i);
}
}
if(diff(beginWord, wordList.get(i))==1){//字典中的当前单词与beginWord可以改变,图中有边
edge[size].add(i);
edge[i].add(size);
}
int temp = diff(endWord, wordList.get(i));
if(temp == 1){//字典中的当前单词与endWord可以改变,图中有边
edge[i].add(size+1);
edge[size+1].add(i);
}
if(temp == 0){//字典中包含endWord,can_arrive
can_arrive = true;
}
}
if(!can_arrive){
return res;
}
//2.上述构好图后,用SPFA最短路算法求beginWord到endWord的最短路径
dist = new int[size+2];//求edge[size]节点的单源最短路径数组
for(int i = 0; i<size+2; i++){
dist[i] = Integer.MAX_VALUE;
}
dist[size] = 1;
Queue<Integer> queue = new LinkedList<>();
queue.offer(size);//起始节点入队
while(!queue.isEmpty()) {
int queFirstNode = queue.poll(); //获取队头
int dest = dist[queFirstNode];
for(int i=0; i<edge[queFirstNode].size(); ++i){//遍历queFirstNode为起点的每条边
//当队头的节点等于边edgeTemp的起点时,进行松弛操作
int temp = (int)edge[queFirstNode].get(i);
if( dist[temp] > dest+1 ) {
dist[temp] = dest+1;
queue.add(temp);
if(temp == size+1) {//该节点是终点,即改变成功,用dfs从终点返回回去找路径
dfs(temp);
return res;
}
}
}
}
return res;
}
public void dfs(int p){
if(p==size){//起点
tempPath.add(a);
}
else if(p==size+1){//终点
tempPath.add(b);
}
else{
tempPath.add(wl.get(p));
}
if(dist[p]==1){//递归出口,如果递归到了起点,说明一条路径已经找到了
ArrayList<String> temp = new ArrayList<>(tempPath);
Collections.reverse(temp);
res.add(temp);
if (!tempPath.isEmpty()){
tempPath.remove(tempPath.size()-1);
}
return;
}
//往回走
for(int i=0; i<edge[p].size(); ++i){
int dest = (int)edge[p].get(i);
if(dist[dest]+1 == dist[p]){
dfs(dest);
}
}
if (!tempPath.isEmpty()){
tempPath.remove(tempPath.size()-1);
}
}
//判断两个字符串不同的字符个数
public int diff(String word1, String word2){
int res = 0;
for(int i=0; i<word1.length(); ++i){
if(word1.charAt(i)!=word2.charAt(i)){
res++;
}
}
return res;
}
public static void main(String[] args){
Solution s = new Solution();
String beginWord = "hit";
String endWord = "cog";
String[] wordList = new String[]{"hot","dot","dog","lot","log","cog"};
System.out.println(s.findLadders(beginWord, endWord, Arrays.asList(wordList)));
//out:[[hit, hot, dot, dog, cog], [hit, hot, lot, log, cog]]
}
}
leetcode 28
题目:Implement strStr().
Return the index of the first occurrence of needle in haystack, or -1 if needle is not part of haystack.
Example 1:
Input: haystack = "hello", needle = "ll"
Output: 2
Example 2:
Input: haystack = "aaaaa", needle = "bba"
Output: -1
思路:kmp算法。求模式串的next数组,然后进行字符串匹配即可。不用kmp算法之前的时间o(mn),m字符串,n模式串,用kmp算法之后的时候是o(m+n)。下面重点讲解kmp中的next数组怎么求。
如果不写代码的话,kmp的next非常好求,就是考研时候的那个套路,如"ABAD"–>next:[-1,0,0,1],有了next数组,拿模式串j和字符串i依次进行匹配即可,如果不等,则i不动,对应模式串跳转到next[j]继续匹配,如果next[j]==-1,则i也+1即可
代码实现next数组的过程强推油管(KMP) Pattern Matching,下面是自己的总结。
核心就是代码求kmp的next数组如"ABAD"–>next:[-1,0,0,1],见下面的代码:
public int strStr(String haystack, String needle) {
if(needle==null || needle.length()==0){
return 0;
}
if(haystack==null || haystack.length()==0){
return -1;
}
int i = 0;
int j = 0;
int[] next = getNext(needle);
while (i < haystack.length() && j < needle.length())
{
if ( j==-1 || haystack.charAt(i)==needle.charAt(j) )
{
i++;
j++;
}
else
j = next[j];
}
if ( j==needle.length() ){//匹配成功,返回needle出现的位置
return i-j;
}
return -1;
}
public int[] getNext(String needle){
int[] next = new int[needle.length()];
int j = -1;
int i = 0;
next[0] = -1;
while(i<needle.length()){
if(j==-1 || needle.charAt(i)==needle.charAt(j)){
j++;
i++;
if(i<needle.length()){
next[i] = j;
}
}
else{
j = next[j];
}
}
return next;
}
leetcode 29
题目:除法运算,但是不能使用/、%、*(除数不会为0)。
思路:将除法转化为减法,但是单纯的做减法计算次数太粗暴,故采用除数扩大(左移),商随之扩大(左移)的思想,同时要考虑正负两边的最大绝对值是不一样的。最简单的方法是用long去处理。另外一个边界条件需要单独处理:-2147483648/-1= 2147483647
把除数表示为:dividend = 2^i * divisor + 2^(i-1) * divisor + … + 2^0 * divisor。这样一来,我们所求的商就是各系数之和了,而每个系数都可以通过移位操作获得。
分两步走:
1)获得i的值;
2)将各系数求和。
显然每步都是logN的复杂度。
代码:
//除法转换为减法。具体思路:除数左移几次,商左移几次。dividend=2^i*divisor+2^(i-1)*divisor+...+2^0*divisor.
public int divide(int dividend, int divisor) {
if(dividend == 0){
return 0;
}
boolean isPositive = (dividend>0 && divisor>0) || (dividend<0 && divisor<0);
long res = divideDetail( Math.abs((long)dividend), Math.abs((long)divisor) );
if(isPositive && res>Integer.MAX_VALUE){//溢出特例必须处理-2147483648/-1= 2147483647
res = Integer.MAX_VALUE;
}
return isPositive ? (int)res : -(int)res;
}
public long divideDetail(long dividend, long divisor){
//1.除数左移, 获取i值
long i = 0;
while(dividend >= (divisor<<1)){
divisor <<= 1;
i++;
}
//2.商左移cnt次,获取多项式系数和,即商系数相加
long res = 0;
while(i>=0){
if(dividend>=divisor){
res += (1L<<i);
dividend -= divisor;
}
divisor >>= 1;
i--;
}
return res;
}
leetcode 69
题目:Implement int sqrt(int x).求x的平方根。
Compute and return the square root of x, where x is guaranteed to be a non-negative integer.
Since the return type is an integer, the decimal digits are truncated and only the integer part of the result is returned.
思路1:二分法。定义一个最小精度,用二分法逼近即可。
//二分法
public int mySqrt(int x) {
if(x == 0){
return 0;
}
if(x<4){
return 1;
}
double left = 1;
double right = x;
double mid;
while(left<=right){
mid = left + (right-left)/2;
if(mid*mid==x || right-mid<1e-9){
return (int)mid;
}
else if(mid*mid>x){//因为是double类型所以乘法比除法好,也不会溢出
right = mid;
}
else if(mid*mid<x){
left = mid;
}
}
return 0;
}
思路1的改进:因为题目中所求是int类型,所以不需要double类型去逼近,用int
//二分法的改进
public int mySqrt(int x) {
if(x == 0){
return 0;
}
if(x<4){
return 1;
}
int left = 1;
int right = x;
int mid;
while(left<=right){
mid = left + (right-left)/2;
if(x/mid==mid){
return mid;
}
else if(x/mid<mid){//因为是int类型所以除法比乘法好,这样也可以避免溢出
right = mid-1;
}
else if(x/mid>mid){
left = mid+1;
}
}
return right;//while跳出循环时,right < left,应该返回right
}
思路2:牛顿法。对x的平方根的值一个猜想y。通过执行一个简单的操作去得到一个更好的猜测:只需要求出y和x/y的平均值(它更接近实际的平方根值)。
//牛顿法
public int mySqrt(int x) {
double k=1.0; //当然猜想的数拿来主义会更快k=0x5f3759df;
while(Math.abs(k*k-x)>0.0001) {//精度不能太高,否则太慢
k=(k+x/k)/2;
}
return (int)k;
}
leetcode 300
题目:Given an unsorted array of integers, find the length of longest increasing subsequence.
Example:
Input: [10,9,2,5,3,7,101,18]
Output: 4
Explanation: The longest increasing subsequence is [2,3,7,101], therefore the length is 4.
思路1(可取,但不够好):DP问题。时间o(n^2)