LeetCode 21-25 题 详解 Java版 ( 万字 图文详解 LeetCode 算法题21-25 =====>>> <建议收藏>)

简介: LeetCode 21-25 题 详解 Java版 ( 万字 图文详解 LeetCode 算法题21-25 =====>>> <建议收藏>)

目录


第21题. Merge Two Sorted Lists

解法一 迭代

解法二 递归

第22题 . Generate Parentheses

1. 题目描述(中等难度)

解法一 暴力破解

解法二

解法三

扩展 卡塔兰数

第23题: Merge k Sorted Lists

解法一 暴力破解

解法二 一列一列比较

解法三 优先队列

解法四 两两合并

解法五 两两合并优化

第24题: Swap Nodes in Pairs

解法一 迭代

解法二 递归

第25题 : Reverse Nodes in k-Group

解法一 迭代

解法二递归

喜欢 请点个 + 关注


第21题. Merge Two Sorted Lists

题目描述(简单难度)10.png


合并两个有序链表。

解法一 迭代

遍历两个链表。

public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
    ListNode h = new ListNode(0);
    ListNode ans=h;
    while (l1 != null && l2 != null) {
        if (l1.val < l2.val) {
            h.next = l1;
            h = h.next;
            l1 = l1.next;
        } else {
            h.next = l2;
            h = h.next;
            l2 = l2.next;
        }
    }
    if(l1==null){
        h.next=l2;
    }
    if(l2==null){
        h.next=l1;
    } 
    return ans.next;
}

时间复杂度:O(m + n)。

空间复杂度:O(1)。

解法二 递归

参考[这里](https://leetcode.wang/Merge Two Sorted Lists)

ListNode mergeTwoLists(ListNode l1, ListNode l2) {
    if(l1 == null) return l2;
    if(l2 == null) return l1;
    if(l1.val < l2.val) {
        l1.next = mergeTwoLists(l1.next, l2);
        return l1;
    } else {
        l2.next = mergeTwoLists(l2.next, l1);
        return l2;
    }
}

时间复杂度:

空间复杂度:

递归看起来,两个字,优雅!但是关于递归的时间复杂度,空间复杂度的求法,先留个坑吧。


第22题 . Generate Parentheses

1. 题目描述(中等难度)


11.png

给一个数字 n ,返回所有合法的括号匹配,刚好和[20题](https://leetcode.wang/leetCode-20-Valid Parentheses.html)相反。

自己没想出来,全部参考 LeetCode 给出的 Solution


解法一 暴力破解

列举所有的情况,每一位有左括号和右括号两种情况,总共 2n 位,所以总共 22n2^{2n}22n 种情况。


public List<String> generateParenthesis(int n) {
    List<String> combinations = new ArrayList();
    generateAll(new char[2 * n], 0, combinations);
    return combinations;
}
public void generateAll(char[] current, int pos, List<String> result) {
    if (pos == current.length) {
        if (valid(current))
            result.add(new String(current));
    } else {
        current[pos] = '(';
        generateAll(current, pos+1, result);
        current[pos] = ')';
        generateAll(current, pos+1, result);
    }
}
public boolean valid(char[] current) {
    int balance = 0;
    for (char c: current) {
        if (c == '(') balance++;
        else balance--;
        if (balance < 0) return false;
    }
    return (balance == 0);
}

时间复杂度:对每种情况判断是否合法需要 O(n),所以时间复杂度是 O(22nn)O(2^{2n}n)O(22nn) 。


空间复杂度:O(22nn)O(2^{2n}n)O(22nn),乘以 n 是因为每个串的长度是 2n。此外这是假设所有情况都符合的时候,但其实不可能都符合,后边会给出更精确的情况。


解法二


解法一中,我们不停的加左括号,其实如果左括号超过 n 的时候,它肯定不是合法序列了。因为合法序列一定是 n 个左括号和 n 个右括号。


还有一种情况就是如果添加括号的过程中,如果右括号的总数量大于左括号的总数量了,后边不论再添加什么,它都不可能是合法序列了。因为每个右括号必须和之前的某个左括号匹配,如果右括号数量多于左括号,那么一定有一个右括号没有与之匹配的左括号,后边不论加多少左括号都没有用了。例如 n = 3 ,总共会有 6 个括号,我们加到 ( ) ) 3 个括号的情况的时候,有 1 个左括号,2 个右括号,此时后边 3 个括号无论是什么,已经注定它不会是合法序列了。


基于上边的两点,我们只要避免它们,就可以保证我们生成的括号一定是合法的了。


public List<String> generateParenthesis(int n) {
    List<String> ans = new ArrayList();
    backtrack(ans, "", 0, 0, n);
    return ans;
}
public void backtrack(List<String> ans, String cur, int left, int right, int n){
    if (cur.length() == n * 2) {
        ans.add(cur);
        return;
    }
    //左括号不要超过 n
    if (left < n)
        backtrack(ans, cur+"(", left+1, right, n);
    //右括号不要超过左括号
    if (right < left)
        backtrack(ans, cur+")", left, right+1, n);
}

时间复杂度:

空间复杂度:

递归的复杂度分析,继续留坑 =.=。


解法三

解法二中是用列举的方法,仔细想想,我们每次用递归的时候,都是把大问题换成小问题然后去解决,这道题有没有这个思路呢?


我们想一下之前的列举过程,第 0 个位置一定会是左括号,然后接着添加左括号或右括号,过程中左括号数一定大于或等于右括号数,当第一次出现左括号数等于右括号数的时候,假如此时的位置是 c 。那么位置 1 到 c - 1 之间一定是合法序列,此外 c + 1 到最后的 2n -1 也是合法序列。而假设总共是 n 组括号,1 到 c - 1 是 a 组括号, c + 1 到 2n - 1 之间则是 n - 1 - a 组括号,如下图


13.png

13.png


14.png


a = 1,b = 1,对应 (())(()) 一种情况,此时 c = 3。15.png


a = 2,b = 0 对应 ((())), (()()) 两种情况,此时 c = 5。

16.png17.png

所以我们如果要想求 n 组括号,只需要知道 a 组和 b 组的情况,然后组合起来就可以了。


看起来我们在迭代 a ,其实本质上是在迭代 c ,c = 2a + 1,迭代 a 从 0 到 n - 1 ,就是迭代 c 从 1 到 2n - 1。看起来 c 都是奇数,其实是可以理解的,因为 0 到 c 间都是一组组的括号, 所以 c 一定是奇数。为什么可以迭代 c ,因为上边说到每一个合法序列都对应着一个 c ,遍历 c 的话,就能得到所有的情况了,看一下代码吧。


public List<String> generateParenthesis(int n) {
    List<String> ans = new ArrayList();
    if (n == 0) {
        ans.add("");
    } else {
        for (int a = 0; a < n; a++)
            for (String left: generateParenthesis(a))
                for (String right: generateParenthesis(n-1-a))
                    ans.add("(" + left + ")" + right);
    }
    return ans;
}

时间复杂度:

空间复杂度:

留坑。


扩展 卡塔兰数


如果这道题不是让你列举所有的情况, 而是仅仅让你输出 n 对应下有多少种合法序列,该怎么做呢?


答案就是 1n+1C2nn\frac{1}{n+1}C^n_{2n}n+11C2nn,也可以写成1n+1(2nn)\frac{1}{n+1}\binom{2n}{n}n+11(n2n)。怎么证明呢?我主要参考了这里,说一下。


我们假设不考虑是不是合法序列,那么就一共有C2nnC^n_{2n}C2nn种情况,然后我们只需要把里边的非法情况减去就可以了,一共有多少种非法情况呢?


首先我们用C2nnC^n_{2n}C2nn就保证了一定是有 n 个左括号,n 个右括号,那么为什么出现了非法序列?


为了方便论述,我们把左括号记为 +1,右括号记为 -1.


ps:下边的 和 都是指两个数的和,不是你和我中的和。


我们假设非法序列的集合是 M ,而非法序列就是列举过程中右括号数比左括号数多了,也就是和小于 0 了,变成 -1 了。这种情况一旦出现,后边无论是什么括号都改变不了它是非法序列的命了。我们将第一次和等于 -1 的时候的位置记为 d 。每一个非法序列一定存在这样一个 d 。然后关键的地方到了!


此时我们把 0 到 d 所有的 -1 变成 1,1 变成 -1,我们将每一个非法序列都这样做,就构成了一个新的集合 N ,并且这个集合 N 一定和 M 中的元素一一对应( N -> M,在集合 N 中第一次出现和为 1 的位置也就是 d ,把 0 到 d 中所有的 -1 变成 1,1 变成 -1 就回到了 M),从而集合 M 的数量就等于集合 N 的数量。集合 N 的数量是多少呢?


我们来分析下集合 N 是什么样的,集合 N 对应的集合 M 原来的序列本来是这样的,在 0 到 d 之间和是 -1 ,也就是 -1 比 +1 多一个,d + 1 到最后的和一定是 1(因为 n 个 +1 和 n 个 -1 的和一定是 0 ,由于 0 到 d 和是 -1,后边的和一定是 1),也就意味着 +1 比 -1 多一个。而在集合 N 中,我们把 0 到 d 的 -1 变成了 +1 ,+1 变成了 -1 ,所以也变成了 +1 比 -1 多一个,所以集合 N 总共就是 +1 比 -1 多 2 个的集合,也就是 n + 1 个 +1 和 n - 1 个 -1 。


所以集合 N 就是 2n 个位置中选 n - 1 个位置放 -1,其他位置放 +1,总共就有 C2nn−1C^{n - 1}{2n}C2nn−1,所以集合 M 也有 C2nn−1C^{n - 1}{2n}C2nn−1种。


所有合法序列就有 C2nn−C2nn−1=1n+1C2nnCn_{2n}-C{n-1}{2n}=\frac{1}{n+1}C^n{2n}C2nn−C2nn−1=n+11C2nn 。


将集合 M 和集合 N 建立了一一映射,从而解决了问题,神奇!!!!!!!!!!其实,这个数列就是卡塔兰数,可以看下维基百科的定义。



18.png


而这个数列,其实除了括号匹配,还有很多类似的问题,其本质是一样的,例如,


2n 个人排队买票,其中 n 个人持 50 元,n 个人持 100 元。每张票 50 元,且一人只买一张票。初始时售票处没有零钱找零。请问这 2n 个人一共有多少种排队顺序,不至于使售票处找不开钱?


对于一个无限大的栈,一共n个元素,请问有几种合法的入栈出栈形式?


P = a1 a2 a3 … an,其中 ai 是矩阵。根据乘法结合律,不改变矩阵的相互顺序,只用括号表示成对的乘积,试问一共有几种括号化方案?


n 个结点可构造多少个不同的二叉树?


… …


更多例子可以看维基百科和这里。


而 Solutin 给出的时间复杂度,其实就是卡特兰数。


19.png


维基百科的给出的性质。

20.png


本以为这道题挺常规的,然后自己一直卡在解法三的理解上,查来查去,竟然查出了卡塔兰数,虽然似乎和解法三也没什么关系,但又开阔了很多思路。解法三分析出来的迭代方法,以及用映射证明卡塔兰数的求法,棒!


第23题: Merge k Sorted Lists

  1. 题目描述(困难难度)


21.png

k 个有序链表的合并。

我们用 N 表示链表的总长度,考虑最坏情况,k 个链表的长度相等,都为 n 。

解法一 暴力破解

简单粗暴,遍历所有的链表,将数字存到一个数组里,然后用快速排序,最后再将排序好的数组存到一个链表里。


public ListNode mergeKLists(ListNode[] lists) {
    List<Integer> l = new ArrayList<Integer>();
    //存到数组
    for (ListNode ln : lists) {
        while (ln != null) {
            l.add(ln.val);
            ln = ln.next;
        }
    }
    //数组排序
    Collections.sort(l);
    //存到链表
    ListNode head = new ListNode(0);
    ListNode h = head;
    for (int i : l) {
        ListNode t = new ListNode(i);
        h.next = t;
        h = h.next;
    }
    h.next = null;
    return head.next;
}

时间复杂度:假设 N 是所有的数字个数,存到数组是 O(N),排序如果是用快速排序就是 O(NlogN)O(Nlog_N)O(NlogN) ,存到链表是 O(N),所以取个最大的,就是 O(NlogN)O(Nlog_N)O(NlogN)。


空间复杂度:新建了一个链表,O(N)。


解法二 一列一列比较



23.png


我们可以一列一列的比较,将最小的一个存到一个新的链表里。

public ListNode mergeKLists(ListNode[] lists) {
    int min_index = 0;
    ListNode head = new ListNode(0);
    ListNode h = head;
    while (true) {
        boolean isBreak = true;//标记是否遍历完所有链表
        int min = Integer.MAX_VALUE;
        for (int i = 0; i < lists.length; i++) {
            if (lists[i] != null) {
                //找出最小下标
                if (lists[i].val < min) {
                    min_index = i;
                    min = lists[i].val;
                }
                //存在一个链表不为空,标记改完 false
                isBreak = false;
            }
        }
        if (isBreak) {
            break;
        }
        //加到新链表中
        ListNode a = new ListNode(lists[min_index].val);
        h.next = a;
        h = h.next;
        //链表后移一个元素
        lists[min_index] = lists[min_index].next;
    }
    h.next = null;
    return head.next;
}

时间复杂度:假设最长的链表长度是 n ,那么 while 循环将循环 n 次。假设链表列表里有 k 个链表,for 循环执行 k 次,所以时间复杂度是 O(kn)。


空间复杂度:N 表示最终链表的长度,则为 O(N)。


其实我们不需要创建一个新链表保存,我们只需要改变得到的最小结点的指向就可以了。

public ListNode mergeKLists(ListNode[] lists) {
    int min_index = 0;
    ListNode head = new ListNode(0);
    ListNode h = head;
    while (true) {
        boolean isBreak = true;
        int min = Integer.MAX_VALUE;
        for (int i = 0; i < lists.length; i++) {
            if (lists[i] != null) {
                if (lists[i].val < min) {
                    min_index = i;
                    min = lists[i].val;
                }
                isBreak = false;
            }
        }
        if (isBreak) {
            break;
        }
        //最小的节点接过来
        h.next = lists[min_index];
        h = h.next;
        lists[min_index] = lists[min_index].next;
    }
    h.next = null;
    return head.next;
}

时间复杂度:假设最长的链表长度是 n ,那么 while 循环将循环 n 次。假设链表列表里有 k 个链表,for 循环执行 k 次,所以时间复杂度是 O(kn)。

空间复杂度:O(1)。


解法三 优先队列


解法二中,我们每次都是取出一个最小的,然后加入一个新的, O(1)的复杂度,再找最小的,O(k) 的复杂度。我们完全可以用一个优先队列。


24.png

我们将优先级定义为数越小优先级越高,如果用堆实现优先队列,这样我们每次找最小不再需要 O(k),而是 O(log(k)),当然这样的话,我们加入新的话不再是 O(1),也需要 O(log(k))。可以看看这里这里


public ListNode mergeKLists(ListNode[] lists) {
        //定义优先队列的比较器
        Comparator<ListNode> cmp;
        cmp = new Comparator<ListNode>() {  
        @Override
        public int compare(ListNode o1, ListNode o2) {
            // TODO Auto-generated method stub
            return o1.val-o2.val;
        }
        };
        //建立队列
        Queue<ListNode> q = new PriorityQueue<ListNode>(cmp);
        for(ListNode l : lists){
            if(l!=null){
                q.add(l);
            }        
        }
        ListNode head = new ListNode(0);
        ListNode point = head;
        while(!q.isEmpty()){
            //出队列
            point.next = q.poll();
            point = point.next;
            //判断当前链表是否为空,不为空就将新元素入队
            ListNode next = point.next;
            if(next!=null){
                q.add(next);
            }
        }
        return head.next;
    }


时间复杂度:假如总共有 N 个节点,每个节点入队出队都需要 log(k),所有时间复杂度是 O(N log(k))。

空间复杂度:优先队列需要 O(k)的复杂度。


解法四 两两合并

利用之前合并两个链表的算法,我们直接两两合并,第 0 个和第 1 个链表合并,新生成的再和第 2 个链表合并,新生成的再和第 3 个链表合并…直到全部合并完。

public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
    ListNode h = new ListNode(0);
    ListNode ans=h;
    while (l1 != null && l2 != null) {
        if (l1.val < l2.val) {
            h.next = l1;
            h = h.next;
            l1 = l1.next;
        } else {
            h.next = l2;
            h = h.next;
            l2 = l2.next;
        }
    }
    if(l1==null){
        h.next=l2;
    }
    if(l2==null){
        h.next=l1;
    } 
    return ans.next;
}
public ListNode mergeKLists(ListNode[] lists) {
    if(lists.length==1){
        return lists[0];
    }
    if(lists.length==0){
        return null;
    }
    ListNode head = mergeTwoLists(lists[0],lists[1]);
    for (int i = 2; i < lists.length; i++) {
        head = mergeTwoLists(head,lists[i]);
    }
    return head;
}

时间复杂度:不妨假设是 k 个链表并且长度相同,链表总长度为 N,那么第一次合并就是 N/k 和 N/k ,第二次合并就是 2 * N/k 和 N/k,第三次合并就是 3 * N/k 和 N / k,总共进行 n - 1 次合并,每次合并的时间复杂度是 O(n),所以总时间复杂度就是O(∑i=1k−1(i∗Nk+Nk))=O(kN)O(\sum_{i=1}^{k-1}(i*\frac{N}{k}+\frac{N}{k}))=O(kN)O(∑i=1k−1(i∗kN+kN))=O(kN),可以将两项分开,N/k 其实是常数,分开的第一项是等差数列。


空间复杂度:O(1)。


解法五 两两合并优化

依旧假设是 k 个链表,合并的过程优化下,使得只需要合并 log(k)次。


27.png

public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
    ListNode h = new ListNode(0);
    ListNode ans=h;
    while (l1 != null && l2 != null) {
        if (l1.val < l2.val) {
            h.next = l1;
            h = h.next;
            l1 = l1.next;
        } else {
            h.next = l2;
            h = h.next;
            l2 = l2.next;
        }
    }
    if(l1==null){
        h.next=l2;
    }
    if(l2==null){
        h.next=l1;
    } 
    return ans.next;
}
public ListNode mergeKLists(ListNode[] lists) {
    if(lists.length==0){
        return null;
    }
    int interval = 1;
    while(interval<lists.length){
        System.out.println(lists.length);
        for (int i = 0; i + interval< lists.length; i=i+interval*2) {
            lists[i]=mergeTwoLists(lists[i],lists[i+interval]);            
        }
        interval*=2;
    }
    return lists[0];
}

时间复杂度:假设每个链表的长度都是 n ,有 k 个链表,记总结点数是 N = n * k,那么时间复杂度就是O(∑i=1log2kN)=O(Nlogk)O(\sum_{i=1}^{log_2k}N)=O(Nlogk)O(∑i=1log2kN)=O(Nlogk)。


空间复杂度:O(1)。


优先队列的运用印象深刻,此外对两两链表的合并,我们仅仅改变了合并的方式就将时间复杂度降低了很多,美妙!


第24题: Swap Nodes in Pairs

题目描述(中等难度)


38.png

给定一个链表,然后两两交换链表的位置。


解法一 迭代

首先为了避免单独讨论头结点的情况,一般先申请一个空结点指向头结点,然后再用一个指针来遍历整个链表。

先来看一下图示:


29.png

30.png

31.png


point 是两个要交换结点前边的一个位置。

public ListNode swapPairs(ListNode head) {
    ListNode dummy = new ListNode(0);
    dummy.next = head;
    ListNode point = dummy;
    while (point.next != null && point.next.next != null) { 
        ListNode swap1 = point.next;
        ListNode swap2 = point.next.next;
        point.next = swap2;
        swap1.next = swap2.next;
        swap2.next = swap1;
        point = swap1;
    }
    return dummy.next;
}

时间复杂度:O(n)。

空间复杂度:O(1)。

解法二 递归

参考这里

自己画了个参考图。31.png

public ListNode swapPairs(ListNode head) {
    if ((head == null)||(head.next == null))
        return head;
    ListNode n = head.next;
    head.next = swapPairs(head.next.next);
    n.next = head;
    return n;
}


递归时间复杂度留坑。

自己开始没有想出递归的算法,每次都会被递归的简洁吸引。另外,感觉链表的一些题,只要画图打打草稿,搞清指向关系,一般不难。


第25题 : Reverse Nodes in k-Group

题目描述(困难难度)


44.png

将一个链表,每 k 个倒置,最后一组不足 k 个就不倒置。

解法一 迭代


关于单链表倒置,我们在第 2 题就讨论过。有了单链表倒置,这道题无非就是用一个循环,每次将 k 个结点取下来,倒置后再接回去,然后再取 k 个,以此循环,到了最后一组如果不足 k 个,不做处理,直接返回头结点就可以了。所以关键就是,指针指来指去,大家不晕掉就好,我做了图示,大家参考一下。


为了将头结点也一般化,我们创建一个 dummy 结点,然后整个过程主要运用三个指针, tail 指针表示已经倒置后的链表的尾部,subhead 指针表示要进行倒置的子链表,toNull 指针为了将子链表从原来链表中取下来。


45.png

一个 while 循环,让 toNull 指针走 k - 1 步使其指向子链表的尾部。中间的 if 语句就是判断当前节点数够不够 k 个了,不够的话直接返回结果就可以了


66.png

将子链表指向 null ,脱离出来。并且用 temp 保存下一个结点的位置。


67.png


然后调用倒置函数,将子链表倒置。88.png

接下来四步分别是,新链表接到 tail(注意下边的图 tail 是更新后的位置,之前 tail 在 dummy 的位置) 的后边;更新 tail 到新链表的尾部,也就是之前的 subhead (下图 subhead 也是更新后的位置,之前的位置参见上边的图);sub_head 更新到 temp 的位置;toNull 到 sub_head 的位置;然后将新的尾部 tail 把之前断开的链表连起来,接到 sub_head 上。


99.png


整理下其实就是下边的样子



0.png

和初始的时候(下边的图)对比一下,发现 tail,subhead 和 toNull 三个指针已经就位,可以愉快的重复上边的步骤了。


image.png

看下代码吧。

public ListNode reverseKGroup(ListNode head, int k) {
    if (head == null)
        return null;
    ListNode sub_head = head;
    ListNode dummy = new ListNode(0);
    dummy.next = head;
    ListNode tail = dummy;
    ListNode toNull = head;
    while (sub_head != null) {
        int i = k;
        //找到子链表的尾部
        while (i - 1 > 0) {
            toNull = toNull.next;
            if (toNull == null) {
                return dummy.next;
            }
            i--;
        }
        ListNode temp = toNull.next;
        //将子链表断开
        toNull.next = null;
        ListNode new_sub_head = reverse(sub_head); 
        //将倒置后的链表接到 tail 后边
        tail.next = new_sub_head;
        //更新 tail 
        tail = sub_head; //sub_head 由于倒置其实是新链表的尾部
        sub_head = temp;
        toNull = sub_head;
        //将后边断开的链表接回来
        tail.next = sub_head;
    }
    return dummy.next;
}
public ListNode reverse(ListNode head) {
    ListNode current_head = null;
    while (head != null) {
        ListNode next = head.next;
        head.next = current_head;
        current_head = head;
        head = next;
    }
    return current_head;
}

时间复杂度:while 循环中本质上我们只是将每个结点访问了一次,加上结点倒置访问的一次,所以总共加起来每个结点其实只访问了 2 次。所以时间复杂度是 O(n)。

空间复杂度:O(1)。


解法二递归

有没有被解法一的各种指针绕晕呢,我们有一个更好的选择,递归,这样看起来就会简洁很多。

public ListNode reverseKGroup(ListNode head, int k) {
    if (head == null)
        return null;
    ListNode point = head;
    //找到子链表的尾部
    int i = k;
    while(i - 1 >0){
        point = point.next;
        if (point == null) {
            return head;
        }
        i--;
    }
    ListNode temp = point.next;
     //将子链表断开
    point.next = null;
    //倒置子链表,并接受新的头结点
    ListNode new_head = reverse(head);
    //head 其实是倒置链表的尾部,然后我们将后边的倒置结果接过来就可以了
    //temp 是链表断开后的头指针,可以参考解法一的图示
    head.next = reverseKGroup(temp,k);
    return new_head;
}
public ListNode reverse(ListNode head) {
    ListNode current_head = null;
    while (head != null) {
        ListNode next = head.next;
        head.next = current_head;
        current_head = head;
        head = next;
    }
    return current_head;
}

复杂度:递归留坑。

还是那句话,涉及到链表的,我们就画下图,把各个指针的移动理清楚,一般没啥问题。

今天我们一起学习了LeetCode 题的算法分析,感谢大家阅读,觉得不错记得收藏哦!

喜欢 请点个 + 关注

目录
相关文章
|
25天前
|
存储 人工智能 算法
数据结构与算法细节篇之最短路径问题:Dijkstra和Floyd算法详细描述,java语言实现。
这篇文章详细介绍了Dijkstra和Floyd算法,这两种算法分别用于解决单源和多源最短路径问题,并且提供了Java语言的实现代码。
62 3
数据结构与算法细节篇之最短路径问题:Dijkstra和Floyd算法详细描述,java语言实现。
|
1月前
|
算法
Leetcode 初级算法 --- 数组篇
Leetcode 初级算法 --- 数组篇
38 0
|
1天前
|
存储 算法 Java
leetcode算法题-有效的括号(简单)
【11月更文挑战第5天】本文介绍了 LeetCode 上“有效的括号”这道题的解法。题目要求判断一个只包含括号字符的字符串是否有效。有效字符串需满足左括号必须用相同类型的右括号闭合,并且左括号必须以正确的顺序闭合。解题思路是使用栈数据结构,遍历字符串时将左括号压入栈中,遇到右括号时检查栈顶元素是否匹配。最后根据栈是否为空来判断字符串中的括号是否有效。示例代码包括 Python 和 Java 版本。
|
23天前
|
算法
每日一道算法题(Leetcode 20)
每日一道算法题(Leetcode 20)
21 2
|
27天前
|
算法 搜索推荐 Java
java 后端 使用 Graphics2D 制作海报,画echarts图,带工具类,各种细节:如头像切割成圆形,文字换行算法(完美实验success),解决画上文字、图片后不清晰问题
这篇文章介绍了如何使用Java后端技术,结合Graphics2D和Echarts等工具,生成包含个性化信息和图表的海报,并提供了详细的代码实现和GitHub项目链接。
92 0
java 后端 使用 Graphics2D 制作海报,画echarts图,带工具类,各种细节:如头像切割成圆形,文字换行算法(完美实验success),解决画上文字、图片后不清晰问题
|
1月前
|
算法 Java 数据中心
探讨面试常见问题雪花算法、时钟回拨问题,java中优雅的实现方式
【10月更文挑战第2天】在大数据量系统中,分布式ID生成是一个关键问题。为了保证在分布式环境下生成的ID唯一、有序且高效,业界提出了多种解决方案,其中雪花算法(Snowflake Algorithm)是一种广泛应用的分布式ID生成算法。本文将详细介绍雪花算法的原理、实现及其处理时钟回拨问题的方法,并提供Java代码示例。
59 2
|
27天前
|
算法 Java Linux
java制作海报一:java使用Graphics2D 在图片上写字,文字换行算法详解
这篇文章介绍了如何在Java中使用Graphics2D在图片上绘制文字,并实现自动换行的功能。
70 0
|
30天前
|
算法 Java
LeetCode(一)Java
LeetCode(一)Java
|
1月前
|
算法 Java 测试技术
数据结构 —— Java自定义代码实现顺序表,包含测试用例以及ArrayList的使用以及相关算法题
文章详细介绍了如何用Java自定义实现一个顺序表类,包括插入、删除、获取数据元素、求数据个数等功能,并对顺序表进行了测试,最后还提及了Java中自带的顺序表实现类ArrayList。
18 0
|
3月前
|
算法
测试工程师的技能升级:LeetCode算法挑战与职业成长
这篇文章通过作者亲身体验LeetCode算法题的过程,探讨了测试工程师学习算法的重要性,并强调了算法技能对于测试职业成长的必要性。
64 1
测试工程师的技能升级:LeetCode算法挑战与职业成长