`
QING____
  • 浏览: 2232908 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

leetcode刷题学习(5)

阅读更多

前言:考虑到本人是能力有限,算法题解只能示例一种本人能够理解和实现的方式,可能不是最优的。

 

一、相交链表(题号:160)

    描述:编写一个程序,找到两个单链表相交的起始节点。

    提示:前提是两个单向链表,而且肯定相交。

 

 

    解题思路:此题比较简单,既然总是相交,我们可以先计算两个链表的长度,从对齐处开始逐个比较。

public class TestMain {

    public static void main(String[] args) {

    }

    private static ListNode execute(ListNode h1,ListNode h2) {
        int l1 = length(h1);
        int l2 = length(h2);

        while (l1 < l2) {
            h2 = h2.next;
            l2--;
        }
        while (l1 > l2) {
            h1 = h1.next;
            l1--;
        };
        while (h1 != h2) {
            h1 = h1.next;
            h2 = h2.next;
        }
        return h1;
    }

    private static int length(ListNode head) {
        int i = 0;
        ListNode next = head;
        while (next != null) {
            i++;
            next = next.next;
        }
        return i;
    }

    static class ListNode {
        int value;
        ListNode next = null;
        ListNode(int value) {
            this.value = value;
        }
    }
}

  

二、最大间距(题号:164)

    描述:给定一个无序数组,找到数组在排序之后,相邻元素之间最大的差值。如果元素个数小于2,则返回0。

 

示例 1:

输入: [3,6,9,1]
输出: 3
解释: 排序后的数组是 [1,3,6,9], 其中相邻元素 (3,6) 和 (6,9) 之间都存在最大差值 3。

示例 2:

输入: [10]
输出: 0
解释: 数组元素个数小于 2,因此返回 0。
 

 

    说明:

    1)你可以假设数组中所有的元素都是非负数,且数值在32为有符号整数范围内。

    2)请尝试在线性时间复杂度和空间复杂度的条件下解决此问题。

    3)元素值,可能有重复出现。

 

    解题思路:这个题,还是有些复杂,原因就在数组是无序的,如果要求复杂度为线性(只跟元素个数有正比关系),那么几乎常见的排序算法,都无法满足。此题的关键是计算“最大间距”,排序或许不是最重要的。

    排序算法中,有一个“桶排序”,本题只需要用到桶排序的思想即可。有关桶排序的详解,可以参考其他资料。(核心思想,非常像hashmap的桶,每个桶表示一个区间)

    1)N个元素的数组,我们使用N + 1个桶 。即额外创建一个长度为N + 1的数组。

    2)找到数组中最大、最小元素,将它们的差值最为值域跨度,值域跨度除以桶个数,就是每个桶需要“承载”的值域。简单来说,我们需要知道每个桶,需要“负责”的数字区间。

    3)因为本地,只需要计算“最大间距”;所以,每个桶中只需要保存其值域区间的最大、最小值即可;每个桶的最大值与下一个桶的最小值之间为“间距”,此桶的最小值与前一个桶的最大值之间为“间距”。

    4)每个桶内部,无论多少个元素,其“间距”总是小于“区间”值。

    5)元素个数为N,桶的个数为N + 1,那么必有一个桶是空的,那么此空桶的前后可能形成最大间距。 

    6)但是,如果临近两个桶,前者为区间的最小值、后者为区间的最大值,仍然有可能产生最大间距(跨了两个区间值)。所以,我们需要比较所有桶之间的“间距”,而不是只关注“空桶”。

 

public class TestMain {

    public static void main(String[] args) {
        int[] source = {3,6,9,1};
        System.out.println(execute(source));
    }

    public static int execute(int[] source) {
        int length = source.length;
        if(length == 1) {
            return 0;
        }
        //找到最大、最小值,用于计算桶的子值域宽度
        int min = source[0];
        int max = min;
        for(int i = 1; i < length; i++){
            min = source[i] < min ? source[i] : min;
            max = source[i] > max ? source[i] : max;
        }
        if(max == min) {
            return 0;
        }
        //桶是否与元素
        boolean[] checkedBucket = new boolean[length + 1];
        //每个桶的,最大值、最小值
        int[] minBucket = new int[length + 1];
        int[] maxBucket = new int[length + 1];

        for(int i = 0; i < length; i++){
            int value = source[i];
            int bid = (value - min) * length / (max - min);
            minBucket[bid] = checkedBucket[bid] ? Math.min(minBucket[bid],value) : value;
            maxBucket[bid] = checkedBucket[bid] ? Math.max(maxBucket[bid],value) : value;
            checkedBucket[bid] = true;
        }

        int maxGap = 0;
        //已经遇到的最大值
        int _max = maxBucket[0];
        for(int i = 1; i <= length; i++){
            if(checkedBucket[i]) {
                //当前桶的最小值,与上一个桶的最大值,之间为"间距"
                maxGap = Math.max(maxGap,minBucket[i] - _max);
                _max = maxBucket[i];
            }
        }
        return maxGap;
    }
}

 

三、分数到小数(题号:166)

    描述:给定两个整数,分别表述分数的分子numerator和分母denominator,以字符串的形式返回小数。如果小数部分为循环小数,则将循环的部分括在括号中。

示例 1:

输入: numerator = 1, denominator = 2
输出: "0.5"
示例 2:

输入: numerator = 2, denominator = 1
输出: "2"
示例 3:

输入: numerator = 2, denominator = 3
输出: "0.(6)"

 

    解题思路:分子分母相除,得到的为结果的整数部分,分子分母取模结果作为分子,继续参与相除,不过此后每次相除每次都 * 10。

public class TestMain {

    public static void main(String[] args) {
        System.out.println(execute(1,2));
        System.out.println(execute(2,1));
        System.out.println(execute(2,3));
    }

    /**
     *
     * @param numerator 分子
     * @param denominator 分母
     * @return
     */
    private static String execute(int numerator, int denominator) {
        if(numerator == 0) {
            return "0";
        }
        if (denominator == 0) {
            return "NaN";
        }
        StringBuilder sb = new StringBuilder();
        //符号判断
        if ((numerator > 0 && denominator < 0) || (numerator < 0 && denominator > 0)) {
            sb.append("-");
        }
        int n = Math.abs(numerator);
        int d = Math.abs(denominator);
        int i = n / d;
        sb.append(i);
        int r = n % d;
        while (r != 0) {
            sb.append(".");
            int x = (r * 10) % d;
            int y = (r * 10) / d;
            if (x == r) {
                sb.append("(").append(y).append(")");
                break;
            }
            sb.append(y);
            r = x;
        }
        return sb.toString();
    }
}

 

四、Excel表列名称(题号:168)

    描述:给定一个正整数,返回它在excel表中对应的列名称。

    

例如,

    1 -> A
    2 -> B
    3 -> C
    ...
    26 -> Z
    27 -> AA
    28 -> AB 
    ...
示例 1:

输入: 1
输出: "A"
示例 2:

输入: 28
输出: "AB"
示例 3:

输入: 701
输出: "ZY"

 

     解题思路:A - Z,共26个字母,其实就是26进制转换问题。跟历史题目中的16进制是一个思路。

public class TestMain {


    public static void main(String[] args) {
        System.out.println(execute(1));
        System.out.println(execute(28));
        System.out.println(execute(701));
    }

    private static String execute(int source) {
        StringBuilder sb = new StringBuilder();
        while (source > 0) {
            int x = (source - 1) % 26;
            sb.append((char)( 'A' + x));
            source = ((source - 1) / 26);//退1,除法退1
        }
        return sb.reverse().toString();
    }
}

 

五、阶乘后的零(题号:172)

    给定一个整数 n,返回 n! 结果尾数中零的数量。

示例 1:

输入: 3
输出: 0
解释: 3! = 6, 尾数中没有零。
示例 2:

输入: 5
输出: 1
解释: 5! = 120, 尾数中有 1 个零.

 

    解题思路:0的个数,取决于2 * 5的组合个数,阶乘从1 到n相乘,2的个数远大于5,因此5的个数决定0的个数;我们只要能够计算出n阶乘中5的个数,就可以得知0的个数。

public class TestMain {

    public static void main(String[] args) {

    }

    public static int execute(int n) {
        int result = 0;
        while (n > 4) {
            n /= 5;//除数中也可能包含5
            result += n;
        }
        return result;
    }
}

 

 

六、二叉搜索树迭代器(题号:173)

    描述:实现一个二叉树迭代器。你将使用二叉搜索树的根节点初始化迭代器。调用next()将返回二叉搜索树的下一个最小的数。

 

 

BSTIterator iterator = new BSTIterator(root);
iterator.next();    // 返回 3
iterator.next();    // 返回 7
iterator.hasNext(); // 返回 true
iterator.next();    // 返回 9
iterator.hasNext(); // 返回 true
iterator.next();    // 返回 15
iterator.hasNext(); // 返回 true
iterator.next();    // 返回 20
iterator.hasNext(); // 返回 false

 

    解题思路:很明显,这是中序遍历的一个非递归操作,为了能够支持迭代,我们唯一能做的,就是先把树中的数据按照中序遍历展开,并存储在一个线性结构中,这个线性结构,你可以使用数组(带游标指针)或者队列中。

public class TestMain {


    public static void main(String[] args) {
        TreeNode root = new TreeNode(7);
        TreeNode n1 = new TreeNode(3);
        TreeNode n2 = new TreeNode(15);
        TreeNode n3 = new TreeNode(9);
        TreeNode n4 = new TreeNode(20);
        root.left = n1;
        root.right = n2;
        n2.left = n3;
        n2.right = n4;
        BSTIterator iterator = new BSTIterator(root);
        while (iterator.hasNext()) {
            System.out.println(iterator.next());
        }
    }

    public static class BSTIterator {
        private Queue<Integer> queue = new LinkedList<>();
        public BSTIterator(TreeNode root) {
            dfs(root);
        }

        public int next() {
            if (queue.isEmpty()) {
                return -1;
            }
            return queue.poll();
        }

        public boolean hasNext() {
            return !queue.isEmpty();
        }

        private void dfs(TreeNode node) {
            if (node == null) {
                return;
            }
            TreeNode left = node.left;
            TreeNode right = node.right;
            int value = node.value;
            if (left != null) {
                dfs(left);
            }
            queue.add(value);
            if (right != null) {
                dfs(right);
            }
        }
    }

    public static class TreeNode {
        private TreeNode left;
        private TreeNode right;
        private int value;
        public TreeNode(int value) {
            this.value = value;
        }
    }
}

 

七、分数排名(题号:178,SQL)

    描述:编写一个 SQL 查询来实现分数排名。如果两个分数相同,则两个分数排名(Rank)相同。请注意,平分后的下一个名次应该是下一个连续的整数值。换句话说,名次之间不应该有“间隔”。

+----+-------+
| Id | Score |
+----+-------+
| 1  | 3.50  |
| 2  | 3.65  |
| 3  | 4.00  |
| 4  | 3.85  |
| 5  | 4.00  |
| 6  | 3.65  |
+----+-------+
例如,根据上述给定的 Scores 表,你的查询应该返回(按分数从高到低排列):

+-------+------+
| Score | Rank |
+-------+------+
| 4.00  | 1    |
| 4.00  | 1    |
| 3.85  | 2    |
| 3.65  | 3    |
| 3.65  | 3    |
| 3.50  | 4    |
+-------+------+

 

    解题思路:这个题的关键是rank排名,且不能有间隙,相同score的rank值一样。我们很容易对score进行排序,但是计算rank需要额外的SQL,所谓rank就是当前score在所有不同score中所对应的倒叙位置;rank的值就是“大于等于当前score值的、且不同的score的个数”。

SELECT Score, (SELECT count(DISTINCT score) FROM Scores WHERE score >= s.score) AS Rank FROM Scores s ORDER BY Score DESC ;

 

八、最大数(题号:179)

    描述:给定一个非负数,重新排列它们的顺序使之组成一个最大的整数。

示例 1:

输入: [10,2]
输出: 210
示例 2:

输入: [3,30,34,5,9]
输出: 9534330

 

    说明:输出结果可能非常大,所以你需要返回一个字符串而不是整数。

    解题思路:这个题,其实很简单,但是我们总是困扰在一些细节上,我本人就是在数据排序上花费了很多时间,其实这些排序SDK API已经有了,直接使用即可。简单来说,按照元素的“字符字面值”排序即可。

public class TestMain {

    public static void main(String[] args) {
        int[] source = {3,30,34,5,9};
        System.out.println(execute(source));
    }

    private static String execute(int[] source) {
        String[] target = new String[source.length];
        for (int i = 0; i < source.length; i++) {
            target[i] = String.valueOf(source[i]);
        }
        Arrays.sort(target);
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < target.length; i++) {
            sb.append(target[i]);
        }
        return sb.reverse().toString();
    }
}

 

 

九、二叉树的右视图(题号:199)

    描述:给定一颗二叉树,想象自己站在它的右侧,按照从顶部到底部的顺序,返回从右侧所能看到的节点值。

示例:

输入: [1,2,3,null,5,null,4]
输出: [1, 3, 4]
解释:

   1            <---
 /   \
2     3         <---
 \     \
  5     4       <---

 

    解题思路:想通的就比较简单,二叉树的广度遍历,只打印最右侧节点。当然也可以增加一个技巧,广度遍历时右节点优先,值打印每层首个节点。

public class TestMain {

    public static void main(String[] args) {
        TreeNode root = new TreeNode(1);
        TreeNode n1 = new TreeNode(2);
        TreeNode n2 = new TreeNode(3);
        TreeNode n3 = new TreeNode(5);
        TreeNode n4 = new TreeNode(4);
        root.left = n1;
        root.right = n2;
        n1.right = n3;
        n2.right = n4;
        List<Integer> result = execute(root);
        System.out.println(result);
    }

    private static List<Integer> execute(TreeNode root) {
        Queue<TreeNode> queue = new LinkedList<>();
        queue.offer(root);
        List<Integer> result = new ArrayList<>();
        while (!queue.isEmpty()) {
            int size = queue.size();//计算当前层的长度

            for (int i = 0;i < size; i++) {
                TreeNode node = queue.poll();
                if (i == 0) {
                    result.add(node.value);
                }
                if (node.right != null) {
                    queue.offer(node.right);
                }
                if (node.left != null) {
                    queue.offer(node.left);
                }
            }
        }
        return result;
    }

    public static class TreeNode {
        private TreeNode left;
        private TreeNode right;
        private int value;
        public TreeNode(int value) {
            this.value = value;
        }

    }
}

 

十、计算质数(题号:204)

    描述:统计所有小于非负整数n的质数的数量。

示例:

输入: 10
输出: 4
解释: 小于 10 的质数一共有 4 个, 它们是 2, 3, 5, 7 。

 

    解题思路:这个题有效的解题方式并不太多,1)直接计算,就是遍历小于n的每个数字,并对每个数字与小于它的所有数字取模、查看结果是否为0,如果有为0的数字则不是质数。 2)思路1的缺点就是耗时,我们可以改进,使用空间换时间,在遍历小于n的数字时,如果它不是质数,则记录下来。

public class TestMain {

    public static void main(String[] args) {
        System.out.println(execute(10));
    }

    private static int execute(int n) {
        boolean[] notPrime = new boolean[n];
        int count = 0;
        for (int i = 2; i < n; i++) {
            if (!notPrime[i]) {
                count++;
                for (int j = 2; i * j < n; j++) {
                    notPrime[i * j] = true;
                }
            }
        }
        return count;
    }

}

 

十一、课程表(题号:207)

    描述:现在你总共有 n 门课需要选,记为 0 到 n-1。在选修某些课程之前需要一些先修课程。 例如,想要学习课程 0 ,你需要先完成课程 1 ,我们用一个匹配来表示他们: [0,1],给定课程总量以及它们的先决条件,判断是否可能完成所有课程的学习?

示例 1:

输入: 2, [[1,0]] 
输出: true
解释: 总共有 2 门课程。学习课程 1 之前,你需要完成课程 0。所以这是可能的。
示例 2:

输入: 2, [[1,0],[0,1]]
输出: false
解释: 总共有 2 门课程。学习课程 1 之前,你需要先完成​课程 0;并且学习课程 0 之前,你还应先完成课程 1。这是不可能的。

 

    说明:

    1)输入的先决条件是由边缘列表表示的图形,而不是邻接矩阵。详情请参见图的表示法。

    2)你可以假定输入的先决条件中没有重复的边。

 

    解题思路:主要是考察拓扑排序(DAG,有向无环图)的设计算法,通过拓扑排序之后,查看此图中是否有“环形依赖”;如果存在环形路径,表示节点之间存在循环依赖,则条件无法达成。这个算法的核心思想,就是背下来。

public class TestMain {

    public static void main(String[] args) {
        int[][] source = {{1,0},{0,1}};
        System.out.println(execute(2,source));
    }

    /**
     *
     * @param number 课程数量,即顶点个数
     * @param source  数组列表,先决条件(顶点为第二个元素)
     * @return 是否可达。false表示为"有环图"不可达
     */
    private static boolean execute(int number, int[][] source) {
        int[] indegree = new int[number];
        //记录每个顶点(题目的前提是,课程ID是连续的,0-n)
        //被指向的次数,最终在删除边的时候,用于决定此顶点,是否还有被指向的边,
        //以判断此节点是否还有前驱节点。
        //如果节点不是数字,我们可能需要使用map来存储。
        for (int[] item : source) {
            indegree[item[0]]++;
        }

        //找到零入度
        Set<Integer> zeroDegree = new HashSet();
        for (int i = 0; i < number; i++) {
            if (indegree[i] == 0) {
                zeroDegree.add(i);
            }
        }
        //如果没有零入度,即基本条件缺失,则返回false
        if (zeroDegree.isEmpty()) {
            return false;
        }

        //根据DAG的设计原则,从图中,
        // 由零入度开始,删除其所有的"边"
        //然后从新找到新的没有"前驱"的顶点(即新的零入度),继续删除
        //直到为空,或者剩余节点均需要前驱(前置条件,false,有环)
        while (!zeroDegree.isEmpty()) {
            Iterator<Integer> it = zeroDegree.iterator();
            int value = it.next();//当前零入度节点
            it.remove();
            for (int[] item : source) {
                //删除,所有零入度的边
                if (item[1] == value) {
                    indegree[item[0]]--;//删除边,其实就是减小被指向次数
                    //如果此节点被指向次数为0,表示其没有前驱引用,则作为新的零入度节点
                    if (indegree[item[0]] == 0) {
                        zeroDegree.add(item[0]);
                    }
                }
            }
        }
        //检测是否还有节点,尚有"前驱引用",如果有,表表示有环。
        //即互相依赖的条件。
        for (int i : indegree) {
            if (i != 0) {
                return false;
            }
        }
        return true;
    }
}

 

  • 大小: 18.5 KB
  • 大小: 11.9 KB
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics