Daily LeetCode – day0007 0623. Add One Row to Tree

class Solution {
    public TreeNode addOneRow(TreeNode root, int val, int depth) {
        if (depth == 1) {
            return new TreeNode(val, root, null);
        }
        Queue<TreeNode> upperFloor = new LinkedList<>();
        int layer = 0;
        upperFloor.add(root);
        while (++layer < depth - 1) {
            int queueSize = upperFloor.size();
            for (int i = 0; i < queueSize; ++i) {
                TreeNode peek = upperFloor.poll();
                if (peek.left != null) {
                    upperFloor.add(peek.left);
                }
                if (peek.right != null) {
                    upperFloor.add(peek.right);
                }
            }
        }
        for (TreeNode node : upperFloor) {
                node.left = new TreeNode(val, node.left, null);
                node.right = new TreeNode(val, null, node.right);
        }
        return root;
    }
}
学习笔记:
这是一道二叉树的题目,也是可以使用深度优先搜索和宽度优先搜索。
深度优先搜索写得代码会特别巧妙,解题的方法自己递归调用自己。
宽度优先搜索也比较符合直觉,把要插入的楼上一层找出来后插入一层。
发表在 每日LeetCode | 留下评论

Daily LeetCode – day0006 1403. Minimum Subsequence in Non-Increasing Order

// 1403. Minimum Subsequence in Non-Increasing Order
class Solution {
    public List<Integer> minSubsequence(int[] nums) {
        int[] buckets = new int[101];
        int sum = 0;
        for (int num : nums) {
            ++buckets[num];
            sum += num;
        }
        int target = sum / 2 + 1;
        sum = 0;
        int i = 100;
        List<Integer> ans = new ArrayList<>();
        while (sum < target) {
            if (buckets[i] != 0) {
                --buckets[i];
                ans.add(i);
                sum += i;
            } else {
                --i;
            }
        }
        return ans;
    }
}
学习笔记:
这道题比去年七夕节的情侣牵手要无聊太多了。
这是一道排序算法和贪心算法的题目。
不过我也探索了很多,Collections.sort()其实会调用Arrays.sort()。Arrays.sort()会根据不同情况选择三种排序,如果是基本类型,则会选择快速排序DualPrivotQuicksort.sort(),如果是引用类型使用的是蒂姆排序TimSort.sort(),除非用户请求的是使用LegacyMergeSort才会用祖传的归并排序Arrays.legacyMergeSort()。Tim排序发明人是Tim Peters,也是Python之禅的作者。有机会一定要研究研究。
这道题用上面这些都不是最佳,都是3ms,而如果用了桶排序,就可以到1ms打败100%的用户。
发表在 每日LeetCode | 留下评论

Daily LeetCode – day0005 0899. Orderly Queue

// 0899. Orderly Queue
class Solution {
    public String orderlyQueue(String s, int k) {
        if (k == 1) {
            String ans = s;
            StringBuilder sb = new StringBuilder(s);
            int loop = s.length();
            while (--loop > 0) {
                sb.append(sb.charAt(0)).deleteCharAt(0);
                if (sb.toString().compareTo(ans) < 0) {
                    ans = sb.toString();
                }
            }
            return ans;
        } else {
            char[] charArray = s.toCharArray();
            Arrays.sort(charArray);
            s = String.valueOf(charArray);
            return s;
        }
    }
}
学习笔记:
StringBuilder有deleteCharAt操作起来非常快,可惜StringBuilder没有compare函数,所以每次对比要转成String去和ans比较,不然只要ans是StringBuilder,最后把ans转String返回就好了。
String转char数组一开始我翻了一个错误,一开始用了getBytes但实际上返回的是byte类型的数组而不是char类型的,排序一样但要转回来麻烦。
char数组转回来的时候我脑子又抽了,用了toString,结果忘记这个不是转去String而是为了打印用的函数。
最后用了valueOf函数,但其实有copyValueOf也可以,而且一模一样,查了资料说是对char数组来说完全一致,只是为了早期的代码不需要再去做无谓的修改就一直保留着这两个函数。
发表在 每日LeetCode | 留下评论

Daily LeetCode – day0004 0622. Design Circular Queue

// 0622. Design Circular Queue
class MyCircularQueue {

    private final int[] nums;
    private int size;
    private final int capacity;
    private int front;
    private int rear;

    public MyCircularQueue(int k) {
        nums = new int[k];
        size = 0;
        capacity = k;
        front = 0;
        rear = 0;
    }

    public boolean enQueue(int value) {
        if (size == capacity) return false;
        nums[rear] = value;
        ++size;
        rear = (rear + 1) % capacity;
        return true;
    }

    public boolean deQueue() {
        if (size == 0) return false;
        --size;
        front = (front + 1) % capacity;
        return true;
    }

    public int Front() {
        if (size == 0) return -1;
        return nums[front];
    }

    public int Rear() {
        if (size == 0) return -1;
        return nums[(rear + capacity - 1) % capacity];
    }

    public boolean isEmpty() {
        return size == 0;
    }

    public boolean isFull() {
        return size == capacity;
    }
}
学习笔记:
这里可以使用数组来实现一个循环队列。
数组大小确定后就不动了,所以可以使用final,然后capacity也确定了也是final。
本来思考的是不用size变量,两个指针都是0开始,但后来发现满和空都是重叠无法判断。
所以做法其实可以数组多开一个位置,这样满就不重叠了。
发表在 每日LeetCode | 留下评论

Daily LeetCode – day0003 1374. Generate a String With Characters That Have Odd Counts

// 1374. Generate a String With Characters That Have Odd Counts
class Solution {
    public String generateTheString(int n) {
        char[] ans = new char[n];
        Arrays.fill(ans, 'b');
        ans[0] = "ab".charAt(n & 1);
        return new String(ans);
    }
}
学习笔记:
这是一道特别简单的字符串题目。
如果n是奇数,就产生n个b。如果是偶数,就将第一位修改成a。
思路就是这样,但如何不通过if语句来玩呢,这里就用了切割一段字符串了,substring可以用,但就一个字符的话用charat最好。接下来判断奇偶用%2是可以,但位运算的&1明显更高级也更快速。
发表在 每日LeetCode | 留下评论

Daily LeetCode – day0002 1161. Maximum Level Sum of a Binary Tree

// 1161. Maximum Level Sum of a Binary Tree
class Solution {
    public int maxLevelSum(TreeNode root) {
        if (root == null) {
            return 0;
        }
        Queue<TreeNode> q = new LinkedList<>();
        q.offer(root);
        int level = 1;
        int ans = 0;
        int levelSum;
        int maxLevelSum = Integer.MIN_VALUE;
        int levelSize;
        while (!q.isEmpty()) {
            levelSum = 0;
            levelSize = q.size();
            for (int i = 0; i < levelSize; ++i) {
                TreeNode peek = q.poll();
                levelSum += peek.val;
                if (peek.left != null) {
                    q.offer(peek.left);
                }
                if (peek.right != null) {
                    q.offer(peek.right);
                }
            }
            if (levelSum > maxLevelSum) {
                maxLevelSum = levelSum;
                ans = level;
            }
            ++level;
        }
        return ans;
    }
}
学习笔记:
这是一道二叉树相关的题目,使用深度优先搜索和宽度优先搜索都可以做。
不过既然是统计每一层内的总值找出最大的层号,用宽度优先搜索来做会更加自然一些。宽度优先搜索会使用到队列,Java中的队列的函数有6个,功能是加入队尾、看一眼队首、移除队首3种,但在遇到队列空了或者满了的特殊情况的应对不同。
抛出异常返回特殊值
加入队尾add()offer()
看一眼队首element()peek()
移除队首remove()poll()
发表在 每日LeetCode | 留下评论

Daily LeetCode – day0001 0952. Largest Component Size by Common Factor

// 0952. Largest Component Size by Common Factor
class Solution {
    public int largestComponentSize(int[] nums) {
        int maxValue = 0;
        for (int num : nums) {
            if (num > maxValue) {
                maxValue = num;
            }
        }
        UnionFind unionFind = new UnionFind(maxValue + 1);
        for (int num : nums) {
            for (int i = 2; i * i <= num; ++i) {
                if (num % i == 0) {
                    unionFind.union(num, i);
                    unionFind.union(num, num / i);
                }
            }
        }
        HashMap<Integer, Integer> hashMap = new HashMap<>();
        for (int num : nums) {
            int parentOfNum = unionFind.find(num);
//            if (hashMap.get(parentOfNum) == null) {
//                hashMap.put(parentOfNum, 1);
//            } else {
//                hashMap.put(parentOfNum, hashMap.get(parentOfNum) + 1);
//            }
            hashMap.merge(parentOfNum, 1, Integer::sum);
        }
        int ans = 1;
        for (Integer componentSize : hashMap.values()) {
            if (componentSize > ans) {
                ans = componentSize;
            }
        }
        return ans;
    }
}

class UnionFind {
    private final ArrayList<Integer> parent = new ArrayList<>();

    public UnionFind(int n) {
        for (int i = 0; i < n; ++i) {
            parent.add(i);
        }
    }

    public int find(int son) {
        if (parent.get(son) == son) {
            return son;
        }
        parent.set(son, find(parent.get(son)));
        return parent.get(son);
    }

    public void union(int a, int b) {
        int parentOfA = find(a);
        int parentOfB = find(b);
        if (parentOfA != parentOfB) {
            parent.set(parentOfA, parentOfB);
        }
    }
}
学习笔记:
这是一道并查集的题目,也考查了对于求因数的基本功。
1.
并查集中难写的是find函数,其中递归调用那段最容易卡住。
先从parent中找到目前son的老大,然后对这个老大再递归调用寻找老大的老大,最终找到后将最大的老大设置到parent中索引为son的位置上。在递归的过程中,中间节点的小老大也会被一起指向最大的老大。
2.
这里用了hashmap来存储每一个给的数字最终老大的小弟数量。
如果一开始没有在hashmap里就认为原本是0,设定为1。
否则就将原本的数字加上1。
这一段我写的比较朴素,使用了if-else语句。但有存在两种高级写法。
merge函数是一种,这个会比较难以理解,还用到了lambda表达式。
getOrDefault函数时另一种,这个函数特别好理解,有就get没有就按default来。

发表在 每日LeetCode | 留下评论

2022.07.30杂谈-Daily LeetCode重新启航

去年夏天做了一个《每日LeetCode》的增强活动,的确在三个月里面增强了很多。自己的数据结构和算法的水平也属于程序员中的中上水准了。但是后来更忙了更累了就懈怠了。

今天休息无聊,看到了力扣的每日一题是一道关于并查集的题目,自己对于并查集的代码的确不够纯属,虽然有教过学生,但自己实现起来也不流畅。

人如果一直呆在舒适区里,便很难进步了,这一次我要继续挑战自己,开启新的旅程。

这次的《Daily LeetCode》会开始使用Java语言,Java可能会没有C++来得精细好用,但是更贴合我目前工作,顺带可以提升我今后工作的效率。

靡不有初,鲜克有终,就让我从《952. 按公因数计算最大组件大小》这道困难题开始挑战吧。

发表在 樊轶群杂谈, 每日LeetCode | 留下评论

用「数学期望」判断麻雀打得对不对

什么是数学期望,大家应该大学都学过,如果没有学过,我也就简单做了介绍。

 

譬如让你投硬币100次,每次都会有正面或反面两种可能性各一半,正面给你100元,反面要罚你80元,那么每次的数学期望是多少呢?

这里的数学期望=(正面概率*正面收益)+(反面概率*反面收益),

也就是(0.5*100)+(0.5*(-80))=10元。

通过单次数学期望,我们可以知道100次的数学期望是赚1000元。

 

接下来回到主题,用「数学期望」判断麻雀打得对不对。

假设我们子家东1局5巡目拿到了这样的一手牌,没有人附露,悬赏牌也没有,

我们要怎么样计算数学期望呢?

其实,任何一局,都只有以下可能:自己和了、自己放铳、别家自摸、横移动、流局。

期望=(和了率*点数)+(放铳率*点数)+(别家自摸率*点数)+(横移动率*点数)+(流局率*点数)

立直:

(0.68*3700)+(0.07*-6600)+(0.08*-2900)+(0.04*-1000)+(0.13*1000)=1912

默听:

(0.85*1100)+(0.04*-5600)+(0.05*-1900)+(0.06*0)+(0.00*2000)=616

有人会说平和nomi默听为什么和了的点数平均是1100呢?

因为有意外情况,比如可能有海底、河底、枪杠,还有收获了追立的人的立直棒,还有有人帮你杠出来了新宝牌。

所以综上所述,这样的情况先制立直的数学期望高1300,遥遥领先默听。

 

但如果时间变化了,不再是5巡目,而是更往后,会有什么用的影响呢?

平和nomi:

5巡目,立直数学期望1900,默听数学期望600。相差1300。

8巡目,立直数学期望1100,默听数学期望200。相差900。

12巡目,立直数学期望300,默听数学期望-300。相差600。

可以看出越往后,平和moni立直和默听的期望值在缩小,但是立直仍然高于默听。

 

正所谓:平和dora一,不立是傻逼。

平和dora一打点只有2000,平自摸dora一也只有2700。

立直可以确定3900的打点,并且有一発和里宝,那么就会有54%概率到7700或更高。

默听打点数学期望2200,立直打点数学期望6300。

接下来,我们再来看这种情况的数学期望。

平和dora一:

5巡目,立直数学期望3600,默听数学期望1600。相差2000。

8巡目,立直数学期望2600,默听数学期望1000。相差1600。

12巡目,立直数学期望1400,默听数学期望400。相差1000。

可以看出立直对于平和dora一的提升非常大。

 

再接下来,我们看平和dora二,或者(断幺、一杯)这些3翻的平和的两面牌。

大多数人计算应该只是打点:

默听打点是3900,自摸5200。

立直打点是7700,自摸8000。

这里主要是3900变7700实在太诱人了,但是和牌率下降放铳率上升的损失能弥补多少呢?

默听打点数学期望4300,立直打点数学期望9100。

5巡目,立直数学期望5500,默听数学期望3400。相差2100。

8巡目,立直数学期望4200,默听数学期望2600。相差1600。

12巡目,立直数学期望2600,默听数学期望1600。相差1000。

这里也可以看出,平和3翻牌和平和dora一立直的提升是几乎一样大,应该立直。

 

既然3900变7700,这种翻倍收益巨大,那么5200变8000的小翻倍需要立直吗?

这也是非常纠结的一点,因为很多雀士主张:5200应该知足了,立直的话很可能就和不了了。也没办法应对后续情况来防守了。

接下来我们来看5200(常见于断幺dora二)的两面听牌,数学期望是怎么样的:

5巡目,立直数学期望5600,默听数学期望4800。相差800。

8巡目,立直数学期望4300,默听数学期望3800。相差500。

12巡目,立直数学期望2600,默听数学期望2500。相差100。

果然,这里立直的数学期望提升较小,但起码不是负值,说明立直还存在理论上的正确性。

 

今天就先记录到这里,以上主要是两面听的数据,单骑听、边张听、嵌张听、双碰听则数据会难看很多。

发表在 樊轶群的麻雀技术 | 一条评论

Ukulele谱《下雨的时候特别想你》陈壹千

发表在 樊轶群Ukulele谱 | 标签为 | 一条评论