thảo luận Leetcode mỗi ngày

  • Người tạo chủ đề Người tạo chủ đề _Gia_Cat_Luong_
  • Ngày bắt đầu Ngày bắt đầu
Trạng thái
Không mở để trả lời thêm.
C-like:
use std::collections::*;

#[derive(Debug)]
pub struct UnionFind {
    parents: Vec<usize>,
    ranks: Vec<usize>,
    set_count: usize,
    set_sizes: Vec<usize>
}

impl UnionFind {
    pub fn new(size: usize) -> Self {
        let mut parents = vec![0; size];

        for i in 0..size {
            parents[i] = i;
        }

        let ranks = vec![0; size];
        let set_sizes = vec![1; size];

        Self {
            parents: parents,
            ranks: ranks,
            set_count: size,
            set_sizes: set_sizes
        }
    }

    pub fn find_set(&mut self, i: usize) -> usize {
        if self.parents[i] == i {
            i
        } else {
            self.parents[i] = self.find_set(self.parents[i]);

            self.parents[i]
        }
    }

    pub fn same_set(&mut self, i: usize, j: usize) -> bool {
        self.find_set(i) == self.find_set(j)
    }

    pub fn union(&mut self, i: usize, j: usize) {
        if self.same_set(i, j) {
            return;
        }

        let mut set_i = self.find_set(i);
        let mut set_j = self.find_set(j);

        if self.ranks[set_i] > self.ranks[set_j] {
            (set_i, set_j) = (set_j, set_i);
        }

        if self.ranks[set_i] == self.ranks[set_j] {
            self.ranks[set_j] += 1;
        }

        self.set_sizes[set_j] += self.set_sizes[set_i];

        self.parents[set_i] = set_j;
        self.set_count -= 1;
    }

    pub fn set_count(&self) -> usize {
        self.set_count
    }

    pub fn set_size(&mut self, i: usize) -> usize {
        let set_i = self.find_set(i);

        self.set_sizes[set_i]
    }
}

impl Solution {
    pub fn max_num_edges_to_remove(n: i32, edges: Vec<Vec<i32>>) -> i32 {
        let un = n as usize;
        let mut auf = UnionFind::new(un);
        let mut buf = UnionFind::new(un);
        let total_edge_count = edges.len();
        let mut used_edge_count = 0;

        for edge in edges.iter().filter(|&edge| edge[0] == 3) {
            let u = edge[1] as usize - 1;
            let v = edge[2] as usize - 1;

            if !auf.same_set(u, v) && !buf.same_set(u, v) {
                auf.union(u, v);
                buf.union(u, v);
                used_edge_count += 1;
            }
        }

        for edge in edges.iter().filter(|&edge| edge[0] == 1) {
            let u = edge[1] as usize - 1;
            let v = edge[2] as usize - 1;

            if !auf.same_set(u, v) {
                auf.union(u, v);
                used_edge_count += 1;
            }
        }

        for edge in edges.iter().filter(|&edge| edge[0] == 2) {
            let u = edge[1] as usize - 1;
            let v = edge[2] as usize - 1;

            if !buf.same_set(u, v) {
                buf.union(u, v);
                used_edge_count += 1;
            }
        }

        if auf.set_count != 1 || buf.set_count != 1 {
            -1
        } else {
            (total_edge_count - used_edge_count) as i32
        }
    }
}


C-like:
impl Solution {
    pub fn odd_even_list(mut head: Option<Box<ListNode>>) -> Option<Box<ListNode>> {
        let mut odd_head = Some(Box::new(ListNode::new(1)));
        let mut odd_tail = odd_head.as_mut();
        let mut even_head = Some(Box::new(ListNode::new(0)));
        let mut even_tail = even_head.as_mut();
        let mut i = 0;

        while let Some(mut node) = head {
            head = node.next.take();

            if i % 2 == 0 {
                even_tail =
                    even_tail.and_then(|mut tail| {
                        tail.next = Some(node);
                        tail.next.as_mut()
                    });
            } else {
                odd_tail =
                    odd_tail.and_then(|mut tail| {
                        tail.next = Some(node);
                        tail.next.as_mut()
                    });
            }

            i += 1;
        }

        even_tail.zip(odd_head).map(|(tail, head)| tail.next = head.next);

        even_head.and_then(|mut head| head.next.take())
    }
}
 
Bài hôm nay khoai quá, search google thì thấy video giải thích này

mà chưa học union find nên xem cũng k hiểu gì :beat_brick:
 
bài hôm nay có phần gần giống với việc dùng thuật toán Kruskal tìm minimum spanning tree, nhưng làm việc trên unweighted graph nên không cần phải sort theo edge weight nữa

hiểu đại khái vậy nhưng vẫn chưa rõ lắm tại sao cách dùng type 3 edge trước rồi add edge có type khác vào sao lại là optimal, có cao nhân nào chỉ giáo không
 
nay cheat giữ chuỗi húp tạm 50 coin full 30 ngày/tháng :pudency:
fake it till you make it :sure: hứa 1 ngày nào đó trong tương lại sẽ quay lại trả món nợ này :pudency:
 
Theo mình nghĩ thì có 3 bước để giải, mà phức tạp quá
B1: trong các cạnh type 3, tạo nên các miền liên thông, trong mỗi miền liên thông, tạo cây khung => tính ra các cạnh type 3 dư
B2: dựa trên kết quả của B1, tạo cây khung của Alice. Ko tạo đc thì return -1. Tạo ra đc cây khung thì tính các cạnh type 1 bị dư.
B3 thì tương tự bước 2.
 
Bữa giờ gặp topo sort hoài mà ko biết nên rảnh ngồi đọc làm thử
JavaScript:
var findAllRecipes = function(recipes, ingredients, supplies) {
    const recipes_set = new Set(recipes);

    // Prepare graph
    const graph = {};
    const inDegree = {};
    for (let i = 0; i < recipes.length; i++) {
        if (recipes[i] in inDegree === false) inDegree[recipes[i]] = 0;
        for (const ing of ingredients[i]) {
            if (ing in graph === false) graph[ing] = [];
            graph[ing].push(recipes[i]);
            inDegree[recipes[i]]++;
        }
    }

    //console.log(graph, inDegree);

    // Topo sort
    // Init topo queue from supplies as it all has zero degree.
    const queue = [...supplies];
    const ans = [];
    
    while (queue.length) {
        let ingred = queue.shift();
        // Only add item which is recipe
        if (recipes_set.has(ingred)) {
            ans.push(ingred);
        }

        if (ingred in graph === false) continue;

        for (const recipe of graph[ingred]) {
            inDegree[recipe]--;
            if (inDegree[recipe] == 0) {
                queue.push(recipe);
            }
        }
    }

    return ans;
};
 
Mã:
// Lists in F# are implemented as singly linked lists,
// which means that operations that access only
// the head of the list are O(1), and element access is O(n).
// https://learn.microsoft.com/en-us/dotnet/fsharp/language-reference/lists

let oddEvenList (list: int list) =
    list
    |> List.indexed
    |> List.partition (fun (i, _) -> i % 2 = 0)
    |> fun (oddList, evenList) ->
        let _, oddList = oddList |> List.unzip
        let _, evenList = evenList |> List.unzip
        oddList @ evenList

// Test with #time directive
[1..10000] |> oddEvenList
Real: 00:00:00.002, CPU: 00:00:00.000, GC gen0: 0, gen1: 0, gen2: 0
val it: int list =
  [1; 3; 5; 7; 9; 11; 13; 15; 17; 19; 21; 23; 25; 27; 29; 31; 33; 35; 37; 39;
   41; 43; 45; 47; 49; 51; 53; 55; 57; 59; 61; 63; 65; 67; 69; 71; 73; 75; 77;
   79; 81; 83; 85; 87; 89; 91; 93; 95; 97; 99; 101; 103; 105; 107; 109; 111;
   113; 115; 117; 119; 121; 123; 125; 127; 129; 131; 133; 135; 137; 139; 141;
   143; 145; 147; 149; 151; 153; 155; 157; 159; 161; 163; 165; 167; 169; 171;
   173; 175; 177; 179; 181; 183; 185; 187; 189; 191; 193; 195; 197; 199; ...]
Cách cài đặt này không thỏa mãn ràng buộc O(1) space, ngoài ra còn quá phức tạp (sử dụng fancy functions không cần thiết).

Bài này thì tư tưởng tail recursion quá rõ ràng nên một cách khác đơn giản hơn như sau:
Mã:
let oddEvenList xs =
    let rec pick (os, es) xs p =
        match xs, p with
        | x :: xs', true -> pick (x :: os, es) xs' false
        | x :: xs', false -> pick (os, x :: es) xs' true
        | _ -> es @ os |> List.rev

    pick ([], []) xs true
Chỉ cần thêm memory space cho đúng một biến bool (là p), nên SC = O(1). Cách cài đặt trên có thể chuyển thành single-line function bằng cách dùng List.fold, như sau:
Mã:
let oddEventList<'t> =
    List.fold (fun (os, es, p) x ->
        if p then (x :: os, es, not p)
        else (os, x :: es, not p)) ([], [], true)
    >> (fun (a, b, _) -> b @ a |> List.rev)
 
Sửa lần cuối:
Cách cài đặt này không thỏa mãn ràng buộc O(1) space, ngoài ra còn quá phức tạp (sử dụng fancy functions không cần thiết).

Bài này thì tư tưởng tail recursion quá rõ ràng nên một cách khác đơn giản hơn như sau:
Mã:
let oddEvenList xs =
    let rec pick (os, es) xs p =
        match xs, p with
        | x :: xs', true -> pick (x :: os, es) xs' false
        | x :: xs', false -> pick (os, x :: es) xs' true
        | _ -> es @ os |> List.rev

    pick ([], []) xs true
Chỉ cần thêm memory space cho đúng một biến bool (là b), nên SC = O(1). Cách cài đặt trên có thể chuyển thành single-line function bằng cách dùng List.fold.
Hay thế, mình ko nghĩ được recursion luôn
 
Bỏ algo lâu quá rồi nay ngồi implement lại DSU TLE lên TLE xuống :adore:

Lại thêm 1 bài có tư tưởng greedy nữa, ý tưởng là tìm thành phần liên thông sử dụng cạnh type 3 trước vì những cạnh này đều có thể được dùng bởi cả Alice và Bob -> Sau đó chạy lại thuật toán cho type 1 và 2 -> cuối cùng là check xem có thằng nào đang chưa tạo được cây khung không
Python:
class Solution:
    def maxNumEdgesToRemove(self, n: int, edges: List[List[int]]) -> int:
        parents = [i for i in range(n)]
        count = [1] * n

        def find(parents, u):
            if parents[u] != u:
                parents[u] = find(parents, parents[u])
            return parents[u]

        def dsu(parents, count, u, v):
            p_u = find(parents, u)
            p_v = find(parents, v)
            if p_u == p_v:
                return 1
            if p_u < p_v:
                parents[p_v] = p_u
                count[p_u] += count[p_v]
                count[p_v] = 0
            else:
                parents[p_u] = p_v
                count[p_v] += count[p_u]
                count[p_u] = 0
            return 0
        
        removed_edges_count = 0
        for edge in edges:
            [t, u, v] = edge
            if t == 3:
                removed_edges_count += dsu(parents, count, u-1, v-1)
        
        parents_A = copy.deepcopy(parents)
        parents_B = copy.deepcopy(parents)
        count_A = copy.deepcopy(count)
        count_B = copy.deepcopy(count)

        for edge in edges:
            [t, u, v] = edge
            if t == 1:
                removed_edges_count += dsu(parents_A, count_A, u-1, v-1)
            elif t == 2:
                removed_edges_count += dsu(parents_B, count_B, u-1, v-1)
        
        if count_A[0] != n or count_B[0] != n:
            return -1
        
        return removed_edges_count
 
Cách cài đặt này không thỏa mãn ràng buộc O(1) space, ngoài ra còn quá phức tạp (sử dụng fancy functions không cần thiết).

Bài này thì tư tưởng tail recursion quá rõ ràng nên một cách khác đơn giản hơn như sau:
Mã:
let oddEvenList xs =
    let rec pick (os, es) xs p =
        match xs, p with
        | x :: xs', true -> pick (x :: os, es) xs' false
        | x :: xs', false -> pick (os, x :: es) xs' true
        | _ -> es @ os |> List.rev

    pick ([], []) xs true
Chỉ cần thêm memory space cho đúng một biến bool (là p), nên SC = O(1). Cách cài đặt trên có thể chuyển thành single-line function bằng cách dùng List.fold, như sau:
Mã:
let oddEventList<'t> =
    List.fold (fun (os, es, p) x ->
        if p then (x :: os, es, not p)
        else (os, x :: es, not p)) ([], [], true)
    >> (fun (a, b, _) -> b @ a |> List.rev)
Thím cho e hỏi phát recursion thì sao tính là O(1) SC được tím nhỉ? Vì bộ nhớ của stack space sẽ tăng theo n nên SC đúng của recursion implementation phải là O(n) chứ nhỉ? :adore:
 
Thím cho e hỏi phát recursion thì sao tính là O(1) SC được tím nhỉ? Vì bộ nhớ của stack space sẽ tăng theo n nên SC đúng của recursion implementation phải là O(n) chứ nhỉ? :adore:
Cài đặt của mình sử dụng tail recursion nên tất cả các lời gọi đều sử dụng chung stack frame.
 
Thím cho e hỏi phát recursion thì sao tính là O(1) SC được tím nhỉ? Vì bộ nhớ của stack space sẽ tăng theo n nên SC đúng của recursion implementation phải là O(n) chứ nhỉ? :adore:
nhìn có vẻ như là tail recursion, và MS nói rằng F# optimize để không có stack growth:

The difference is that in countDown1 the returned value of the recursive call was used to construct the final value of the function by adding 1 to it. In contrast, in countDown2 the returned value of the recursive call is also the value of the function itself. Meaning, the recursive call is the last instruction in the function definition – a style known as tail recursion. This style allowed the compiler to transform the function into a loop, thus eliminating the need to create new stack frames.
 
Java:
class EdgeComparator implements Comparator<int[]> {
    @Override
    public int compare(int[] a, int[] b) {
        if (a[0] == b[0]) {
            if (a[1] == b[1]) {
                return a[2] - b[2];
            }

            return a[1] - b[1];
        }
        return - (a[0] - b[0]);
    }
}
class Solution {
    public static int TYPE = 0;
    public static int U = 1;
    public static int V = 2;
    public static int ALICE = 1;
    public static int BOB = 2;
    public static int BOTH = 3;

    public int maxNumEdgesToRemove(int n, int[][] edges) {
        List<int[]> bobGraph = getEdgesByType(BOB, edges);
        List<int[]> aliceGraph = getEdgesByType(ALICE, edges);

        List<int[]> bobSPT = getSPTFromGraph(bobGraph, n);
        List<int[]> aliceSPT = getSPTFromGraph(aliceGraph, n);
        if (bobSPT == null || aliceSPT == null) return -1;

        Set<int[]> distincEdge = new TreeSet<>(new EdgeComparator());
        distincEdge.addAll(bobSPT);
        distincEdge.addAll(aliceSPT);
        System.out.println(distincEdge);

        return edges.length - distincEdge.size();
    }

    public List<int[]> getEdgesByType(int type, int[][] edges) {
        List<int[]> graph = new ArrayList<>();
        for (int[] edge : edges) {
            if (type == edge[TYPE] || BOTH == edge[TYPE]) {
                graph.add(edge);
            }
        }

        Collections.sort(graph, new EdgeComparator());

        return graph;
    }

    int[] parent;
    public List<int[]> getSPTFromGraph(List<int[]> edges, int n) {
        List<int[]> sptEdge = new ArrayList<>();
        parent = new int[n + 1];

        for(int i = 1 ; i <= n ; ++i) makeSet(-i);

        for (int[] edge : edges) {
            int u = Math.abs(findSet(edge[U]));
            int v = Math.abs(findSet(edge[V]));
            if(u != v) {
                parent[u] = v;
                sptEdge.add(new int[]{edge[TYPE], edge[U], edge[V]});
            }
        }

        Set<Integer> hashSet = new HashSet<>();
        for (int i = 1; i <= n; i++) {
            hashSet.add(findSet(i));
        }

        if (hashSet.size() != 1) {
            return null;
        }

        return sptEdge;
    }

    void makeSet(int u){
        u = Math.abs(u);
        parent[u] = u;
    }

    int findSet(int u){
        u = Math.abs(u);
        if(u == parent[u]) return u;
        return parent[u] = findSet(parent[u]);
    }
}
Lâu không code đồ thị nên quên hết Kruskal mất công ngồi mo` lại?
 
Mã:
// Lists in F# are implemented as singly linked lists,
// which means that operations that access only
// the head of the list are O(1), and element access is O(n).
// https://learn.microsoft.com/en-us/dotnet/fsharp/language-reference/lists

let oddEvenList (list: int list) =
    list
    |> List.indexed
    |> List.partition (fun (i, _) -> i % 2 = 0)
    |> fun (oddList, evenList) ->
        let _, oddList = oddList |> List.unzip
        let _, evenList = evenList |> List.unzip
        oddList @ evenList

// Test with #time directive
[1..10000] |> oddEvenList
Real: 00:00:00.002, CPU: 00:00:00.000, GC gen0: 0, gen1: 0, gen2: 0
val it: int list =
  [1; 3; 5; 7; 9; 11; 13; 15; 17; 19; 21; 23; 25; 27; 29; 31; 33; 35; 37; 39;
   41; 43; 45; 47; 49; 51; 53; 55; 57; 59; 61; 63; 65; 67; 69; 71; 73; 75; 77;
   79; 81; 83; 85; 87; 89; 91; 93; 95; 97; 99; 101; 103; 105; 107; 109; 111;
   113; 115; 117; 119; 121; 123; 125; 127; 129; 131; 133; 135; 137; 139; 141;
   143; 145; 147; 149; 151; 153; 155; 157; 159; 161; 163; 165; 167; 169; 171;
   173; 175; 177; 179; 181; 183; 185; 187; 189; 191; 193; 195; 197; 199; ...]
Nhân tiện ở đây, ngoài cài đặt sử dụng tail recursion đã trình bày ở post trước, mình trình bày một cài đặt sử dụng kỹ thuật continuation rất hay gặp trong functional programming:
Mã:
let oddEventList xs =
    let rec pickcps xs k =
        match xs with
        | x :: x' :: xs'' -> pickcps xs'' (fun (os, es) -> k (x :: os, x' :: es))
        | x :: _ -> pickcps [] (fun (os, es) -> k (x :: os, es))
        | _ -> ([], []) |> k |> (fun (a, b) -> a @ b)

    pickcps xs id

Cài đặt này không cần sử dụng bất cứ fancy functions nào tuy nhiên sẽ là khó hiểu nếu chưa quen. Kỹ thuật ở đây là thay accumulator trong cài đặt trước bởi một continuation k.
 
C#:
public class Solution
{
    public int MaxNumEdgesToRemove(int n, int[][] edges)
    {
        UnionFind alice = new(n);
        UnionFind bob = new(n);

        for (int i = 0; i < edges.Length; i++)
        {
            int[] edge = edges[i];
            if (edge[0] == 3)
            {
                alice.Union(edge[1], edge[2]);
                bob.Union(edge[1], edge[2]);
            }
        }
        int result = alice.redundant;
        alice.redundant = 0;
        bob.redundant = 0;

        for (int i = 0; i < edges.Length; i++)
        {
            int[] edge = edges[i];
            int type = edge[0];
            int u = edge[1];
            int v = edge[2];
            if (type == 1)
            {
                alice.Union(u, v);
            }
            if (type == 2)
            {
                bob.Union(u, v);
            }
        }

        if (alice.GetComponentCount() != 1 || bob.GetComponentCount() != 1)
        {
            return -1;
        }
        return result + alice.redundant + bob.redundant;
    }

    public class UnionFind
    {
        int[] parent;
        public int redundant = 0;
        
        public int GetComponentCount()
        {
            int componentCount = 1;
            for (int i = 1; i < parent.Length; i++)
            {
                int parent = Find(i);
                componentCount += parent == 1 ? 0 : 1;
            }

            return componentCount;
        }

        public UnionFind(int n)
        {
            parent = new int[n + 1];
            for (int i = 1; i <= n; i++)
            {
                parent[i] = i;
            }
        }

        public int Find(int a)
        {
            if (parent[a] == a)
            {
                return parent[a];
            }

            int result = Find(parent[a]);
            parent[a] = result;
            return result;
        }

        public void Union(int a, int b)
        {
            int aParent = Find(a);
            int bParent = Find(b);
            if (aParent == bParent)
            {
                redundant++;
                return;
            }
            if (aParent < bParent)
            {
                parent[bParent] = aParent;
                return;
            }

            parent[aParent] = bParent;
        }
    }
}
 
bài hôm nay có phần gần giống với việc dùng thuật toán Kruskal tìm minimum spanning tree, nhưng làm việc trên unweighted graph nên không cần phải sort theo edge weight nữa

hiểu đại khái vậy nhưng vẫn chưa rõ lắm tại sao cách dùng type 3 edge trước rồi add edge có type khác vào sao lại là optimal, có cao nhân nào chỉ giáo không
Đầu bài yêu cầu loại đi tối đa các cạnh nhưng vẫn đảm bảo tính liên thông của hai đồ thị.
Ta có thể hiểu là tìm số cạnh ít nhất để cho 2 đồ thị liên thông.
Cạnh loại 3 là cạnh chung của hai đồ thị, do đó ta sẽ muốn ưu tiên giữ lại cạnh loại 3.
 
Trạng thái
Không mở để trả lời thêm.

Thống kê chủ đề

Ngày tạo
_Gia_Cat_Luong_,
Người trả lời cuối
Vipluckystar,
Trả lời
17.755
Lượt xem
1.212.714
Quay lại
Lên đầu trang