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.
Cuối cùng cũng xong cách O(log^2 n)

https://leetcode.com/submissions/detail/806772612/


1663931094203.png


Ngoài việc chia các đoạn với các bit nhỏ. Thì trong trường hợp n không phải dạng 2^k-1 cần phải chia tiếp như sau.

Giả sử n = 1001'0011. Tính từ bit 1 thứ 2 từ phải sang:
  • 1001'0011: [1000'0000 -> 1000'1111]
  • 1001'0011: [1001'0000 -> 1001'0001]
  • 1001'0011: [1001'0010 -> 1001'0011]

Tẩ cả các đoạn trên đều có độ dài dạng 2^k và có thể áp dụng thuật toán O(k) để tính tổng.

C++:
#define M 1'000'000'007
// return 2^x % M
uint64_t powM(uint64_t p, uint64_t x) {
    uint64_t base = p, res = 1;
    while (x) {
        if (x & 1) res = (res * base) % M;
        base = (base * base) % M;
        x >>= 1;
    }
    return res;
}

uint64_t prod(uint64_t x, uint64_t y) {
    return (x * y) % M;
}

uint64_t sum(uint64_t x, uint64_t y) {
    x += y;
    return x - (M & -(x >= M));
}

uint64_t diff(uint64_t x, uint64_t y) {
    int d = (int) x - (int) y;
    return d + (M & -(d < 0));
}

class Solution {
    // k: number bit
    // g: gap
    // pk: 4^(k*(2^(k-2))) - 1
    // p2gk: 2^(gk)
    // -> sum from [end - l + 1 .. end]
    uint64_t helper(uint64_t k, uint64_t pk, uint64_t p2gk, uint64_t l, uint64_t end, uint64_t g = 1)
    {
        if (g == l) return end;

        auto p4gk = prod(p2gk, p2gk); // 4^(gk)
        auto s1 = helper(k, pk, p4gk, l, end, g << 1);

        // s0 = 2^(gk) { s1 - g * [4^(k*2^k) - 1] / [4^(gk) - 1] }
        auto s0 = prod(
            p2gk, // 2^(gk)
            diff (
                s1,
                prod(
                    g,
                    prod(
                        pk,
                        powM(diff(p4gk, 1), M-2) // invmod of 4^(gk) -1 = (4^(gk) - 1) ^ (M - 2)
                    )
                )
            )
        );

        return sum(s0, s1);
    }

public:
    int concatenatedBinary(int n) {
    if (n == 1) return 1;

        uint64_t p2k = 4, // 2^k
                 p42k2 = 4, // 4^(2^(k-2)) = 2^(2^(k-1))
         res = 1,
                 k = 2;
        // sum from [1 .. 2^k)
        for (; p2k <= n + 1;
             p2k = sum(p2k, p2k), p42k2 = prod(p42k2, p42k2), ++k)
        {
            auto pk = powM(p42k2, k); // 4^(k*2^(k-2))
            // p2gk = 2^(gk) = 2^k = p2k
            // calculate sum from [ 2^(k-1) .. 2^k )
            auto h = helper(k, diff(pk, 1), p2k, 1 << (k - 1), (1 << k) - 1, 1);
            // res = h + res * 2^(k * 2^(k-1)) = res + h * pk
            res = sum(prod(res, pk), h);
        }

        // return if n = 2^k-1
        if (!(n & (n + 1))) return res;

        // sum from [2^k .. n)
        // check every 1-bit of n
        for (int kk = k - 2; kk >= 1; --kk) {
            if (n & (1 << kk)) {
                auto l = 1 << kk;
                auto pk = powM(p2k, l);
                auto h = helper(k, diff(pk, 1), p2k, l, (n ^ l) | (l - 1), 1);
                res = sum(prod(res, pk), h);
            }
        }

        // last bit
        if (n & 1) res = sum(prod(res, p2k), n^1);
        res = sum(prod(res, p2k), n);
        return res;
    }
};
 
Thuật toán O(log^2 n). Nguyên tắc cơ bản là dựa vào việc có thể tính nhanh tổng dãy cấp số nhân 2^k phần tử.
Có ai rảnh thử code xem:


Xét nửa khoảng từ [2^k, 2^(k+1) ), ta thấy tất cả số j trong này đều có k bit trong biểu diễn nhị phân. Như vậy khi ghép lại có thể tách làm 2 phân so le k bit như sau (i = 2^k)

s(0) = i 0^k (i+2) 0^k ... i+2^k-2 0^k
s(1) = 0^k (i+1) 0^k i+3 ... 0^k i+2^k-1

Để ý thấy các thành phần tương ứng trong 2 nửa chỉ sai khác nhau 1. Tức là có thể tính một nửa dựa trên nửa còn lại:

s(0) = 2^k * [ s(1) - 1 - 2^2k - 2^4k - ... - 2^(k*(2^k-2)) ]
= 2^k * [ s(1) - (1 + (4^k) + (4^k)^2 + ... + (4^k)^(2^(k-1)-1) )]
= 2^k * [ s(1) - ((4^k)^(2^(k-1)) - 1) / (4^k - 1)]

Thuật toán sẽ là:
  • Tính riêng tổng cho những đoạn có cùng số bit. Có tất cả log n đoạn,
  • Với mỗi đoạn thì dùng phương pháp chia hai như trên. Đệ quy mất O( k)
toy đọc ko hiểu gì hết
Dcnffay.png
 
toy đọc ko hiểu gì hết
Dcnffay.png

Ví dụ đơn giản:
  • giả sử cần tính s = 12345678
  • Có thể chia ra s = a0 + a1: a0 = 10305070, a1 = 02040608
  • khi đó: a0 = 10* (a1 - 01010101).
  • 01010101 = 100^0 + 100^1 + 100^2 + 100^3 = (100^4 - 1) / (100 - 1)

Để tính a1 thì làm tương tự, cứ chia đôi ra cho đến khi chỉ còn 1 số.
 
sao ko tính trước
b<=1 --> 1
b<=2 --> 1_10.11
b<=3 --> 1_10.11_100.101.110.111
b<=4 --> 1_10.11_100.101.110.111_1000.1001.1010.1011.1100.1101.1110.1111
v.v... 17 số như vậy vì n <= 10^5

mảng khác
b==1 --> 1
b==2 --> 10.11
b==3 --> 100.101.110.111
b==4 --> 1000.1001.1010.1011.1100.1101.1110.1111

ròi tính
ví dụ n=12=0b1100 thì số 1 cao nhứt ở index 3, lấy số tính trước ở mảng <= là b<=3: 1_10.11_100.101.110.111, nhân với 2^((3+1) * (1+0b100)) = 2^20 ra 1_10.11_100.101.110.111_0000.0000.0000.0000.0000, rồi cộng số thứ (3+1) ở mảng == là b==4 được dịch phải, hay chia 2^((3+1) * (2^3 - 1 - 0b100)) = 2^12 là số 1000.1001.1010.1011.1100 là ra
uq1dgnk.png


bước dịch trái x bit mod 1e9+7 thì tính dễ, bước dịch phải y bit mod 1e9+7 thì vì 1e9+7 là số nguyên tố nên xài mod inverse là ra
uq1dgnk.png


---

edit: xao toy code thử ko ra đúng
CdMjaWs.gif

edit mod_inverse ko phải là phép dịch bit
LTT2cUR.png
LTT2cUR.png
LTT2cUR.png
 
Sửa lần cuối:
anh ví dụ với k=3 là 100.101.110.111, tô màu Xem tệp đính kèm 1399044 s0 s1 gì xem toy đọc ko hiểu gì
4gmOAMB.png

k = 3

B1: gap = 1, cần tính s = 100.101.110.111
s1
= 000.101.000.111
s0 = 100.000.110.000

==> s0 = (s1 - 000.001.000.001) << 3

B2: gap = 2, cần tính s = 000.
101.000.111 (tức = s1 của B1)
s1 = 000.000.000.111
s0 = 000.101.000.000

==>
s0 = (s1 - 000.000.000.010) << (3 * 2)

B3: gap = 4, cần tính s = 000.000.000.
111
Đây là trường hợp cơ bản, khi gap = 2^(k-1) nên không chia được nữa, trả về luôn bằng
111
 
k = 3

B1: gap = 1, cần tính s = 100.101.110.111
s1
= 000.101.000.111
s0 = 100.000.110.000

==> s0 = (s1 - 000.001.000.001) << 3

B2: gap = 2, cần tính s = 000.
101.000.111 (tức = s1 của B1)
s1 = 000.000.000.111
s0 = 000.101.000.000

==>
s0 = (s1 - 000.000.000.010) << (3 * 2)

B3: gap = 4, cần tính s = 000.000.000.
111
Đây là trường hợp cơ bản, khi gap = 2^(k-1) nên không chia được nữa, trả về luôn bằng
111
cũng hơi hack não nhưng toy đã thấy được pattern, vậy giờ xao mò tới được ví dụ 1000.1001.1010.1011.1100 của n=12 như example 3 ở LC
4gmOAMB.png
hay ở đây là phần tử thứ 5 của k=4
kH9BFd2.gif


edit toy đọc thấy code của anh có xài invmod xao lại xài được nhỉ
MjfezZB.png
à chỗ invmod này ko liên quan tới tìm phần tử thứ mấy mà còn đang ở tính s0
 
Sửa lần cuối:
cũng hơi hack não nhưng toy đã thấy được pattern, vậy giờ xao mò tới được ví dụ 1000.1001.1010.1011.1100 của n=12 như example 3 ở LC
4gmOAMB.png
hay ở đây là phần tử thứ 5 của k=4
kH9BFd2.gif

Cái này chia làm 2 giai đoạn.
  • Tổng của các số ít bit hơn n
  • Tổng của các số bằng số bit. Tức là từ [2^k, n]

Lúc này thì: n = 12 = 1100, 2^k = 2^3 = 1000
Đoạn [1000, 1100] có thể chia thành các đoạn dựa vào ví trí bit 1:

- n = 1100 => [1000, 1011]. Cách làm thì cũng giống như trên, với k = 4. Nhưng ở bước đệ quy cuối thay vì so gap với 2^(k-1) thì so với len là độ dài đoạn này.

Cần tính:
s = 1000.1001.1010.1011
k = 4
len = 4

B1: gap = 1, cần tính s =1000.1001.1010.1011
s1 = 0000.1001.0000.1011
s0 =1000.0000.1010.0000

==> s0 = (s1 - 0000.0001.0000.0001) << 4

B2: gap = 2, cần tính s = 0000.1001.0000.1011 (tức = s1 của B1)
s1 = 0000.0000.0000.1011
s0 = 0000.1001.0000.0000

==> s0 = (s1 - 0000.0000.0000.0010) << (4 * 2)

B3: gap = 4, cần tính s =0000.0000.0000.1011
Đây là trường hợp cơ bản, khi gap =len nên không chia được nữa, trả về luôn bằng 1011


- [1100]: đoạn cuối, xử lý riêng cho n.

-------


Edited: giờ nghĩ lại thấy cắt đôi ra tính đơn giản hơn chia so le như này.

B1: cần tính s =1000.1001.1010.1011
s1 = 0000.0000.1010.1011
s0 = 1000.1001.0000.0000

==> s0 = (s1 - 0000.0000.0010.0010) << 8
 
Sửa lần cuối:
O(n) @@

mò bằng công thức fn = fn-1 * (2 ** (len((bin(n))) - 2)) + n

Python:
class Solution:
    def concatenatedBinary(self, n: int) -> int:
        index = 1
        val = 1
        f = 0
       
        while index < n + 1:
            for i in range(val):
                f = ((f % 1000000007) * val * 2 + index) % 1000000007
                index += 1
                if index == n + 1:
                    break
            val *= 2
            val %= 1000000007
           
        return f
 
Cái này chia làm 2 giai đoạn.
  • Tổng của các số ít bit hơn n
  • Tổng của các số bằng số bit. Tức là từ [2^k, n]

Lúc này thì: n = 12 = 1100, 2^k = 2^3 = 1000
Đoạn [1000, 1100] có thể chia thành các đoạn dựa vào ví trí bit 1:

- n = 1100 => [1000, 1011]. Cách làm thì cũng giống như trên, với k = 4. Nhưng ở bước đệ quy cuối thay vì so gap với 2^(k-1) thì so với len là độ dài đoạn này.

Cần tính:
s = 1000.1001.1010.1011
k = 4
len = 4

B1: gap = 1, cần tính s =1000.1001.1010.1011
s1 = 0000.1001.0000.1011
s0 =1000.0000.1010.0000

==> s0 = (s1 - 0000.0001.0000.0001) << 4

B2: gap = 2, cần tính s = 0000.1001.0000.1011 (tức = s1 của B1)
s1 = 0000.0000.0000.1011
s0 = 0000.1001.0000.0000

==> s0 = (s1 - 0000.0000.0000.0010) << (4 * 2)

B3: gap = 4, cần tính s =0000.0000.0000.1011
Đây là trường hợp cơ bản, khi gap =len nên không chia được nữa, trả về luôn bằng 1011


- [1100]: đoạn cuối, xử lý riêng cho n.

-------


Edited: giờ nghĩ lại thấy cắt đôi ra tính đơn giản hơn chia so le như này.

B1: cần tính s =1000.1001.1010.1011
s1 = 0000.0000.1010.1011
s0 = 1000.1001.0000.0000

==> s0 = (s1 - 0000.0000.0010.0010) << 8
ờm toy hiểu ròi
dzipaLk.gif
thặc vi diệu
dzipaLk.gif
dzipaLk.gif


edit: cắt đôi: https://leetcode.com/submissions/detail/806942199/ thặc kinh quàng 0ms
kH9BFd2.gif

viết cái struct Int tránh % 1e9+7 tùm lum cho code trong xáng
cgE9MkI.gif


rút gọn cái
QwJ0V0V.png
chạy mãi toàn 4ms 7ms giờ mới ra 0ms
8ZdN5i4.gif
https://leetcode.com/submissions/detail/806948593/
edit: đi từ 1000 -> 1000.1001 -> 1000.1001.1010.1011 cũng được mà đâu cần đi ngược
MjfezZB.png

edit: chỗ khởi tạo explicit Int(int value) : value{value} {} đáng lẽ phải có % kMod nhưng trong bài này ko có value nào >= kMod nên ko % cho lẹ, nếu ko có %kMod chỗ khởi tạo thì struct Int này ko reuse cho bài khác được

edit: sửa lại "nhân đôi", đi từ trái qua phải, chỉ cần operator<< và operator+
irGoYrZ.gif

https://leetcode.com/submissions/detail/806958957/

C++:
constexpr int kMod = 1'000'000'007;

struct Int {
    int value;
    explicit Int(int value) : value{value} {}
    Int& operator<<=(int bits) {
        for (long long k = 2; bits; bits >>= 1, k = k * k % kMod)
            if (bits % 2) value = value * k % kMod;
        return *this;
    }
    Int& operator+=(const Int& other) {
        value = (value + other.value) % kMod;
        return *this;
    }
    Int operator<<(int bits) const { return Int{*this} <<= bits; }
    Int operator+(const Int& other) const { return Int{*this} += other; }
};

struct Solution {
    Int concat_k_bits_nums(int k, int startIndex, int size) { // size must be power of 2: 1, 2, 4, 8, 16, ...
        auto s0 = Int{(1 << (k - 1)) + startIndex};
        auto add = Int{1};
        for (int sz = size, bits = k; sz >>= 1; add <<= 1, add += add << bits, bits *= 2) s0 += (s0 << bits) + add;
        return s0;
    }
    int concatenatedBinary(int n) {
        static constexpr int kLe[] = {0,         1,         27,        113015,    35297621,  144699479,
                                      590971296, 5842299,   313471718, 203851921, 396016205, 18825033,
                                      886701872, 950369496, 724626253, 351669993, 183542430};
        const int k = 32 - __builtin_clz(++n);
        auto res = Int{kLe[k - 1]};
        for (int m = 1 << k >> 1, startIndex = 0; m >>= 1;)
            if (n & m)
                res = (res << k * m) + concat_k_bits_nums(k, startIndex, m), startIndex += m;
        return res.value;
    }
};


edit: đi từ trái qua phải có phải logic hơn ko, như viết cái truth table
0
1
10
11
100
101
110
111
thì 10, 11 lấy từ 0, 1 thêm số 1 đằng trước. 100, 101, 110, 111 là 4 số 00, 01, 10, 11 thêm số 1 vào trước. Bác bribnt vẽ ra số so le ko hiểu gì
aVVa2xy.png


edit: https://leetcode.com/submissions/detail/807141722/ constexpr everything
cgE9MkI.gif

https://godbolt.org/z/eb8ah8ePv lang thượng đẳng ko cần magic năm bờ gì cả nhóe
JiZo9zf.png
JiZo9zf.png

1663980335189.png
 
Sửa lần cuối:
1 dòng là đủ rồi, :p
Python:
    def pathSum(self, root: Optional[TreeNode], targetSum: int, curList = list()) -> List[List[int]]:
        return [] if not root \
                else [curList + [root.val]] if root.val == targetSum and not root.left and not root.right \
                else self.pathSum(root.left, targetSum - root.val, curList + [root.val]) \
                + self.pathSum(root.right, targetSum - root.val, curList + [root.val])
 
ờm toy hiểu ròi
dzipaLk.gif
thặc vi diệu
dzipaLk.gif
dzipaLk.gif


edit: cắt đôi: https://leetcode.com/submissions/detail/806942199/ thặc kinh quàng 0ms
kH9BFd2.gif

viết cái struct Int tránh % 1e9+7 tùm lum cho code trong xáng
cgE9MkI.gif


rút gọn cái
QwJ0V0V.png
chạy mãi toàn 4ms 7ms giờ mới ra 0ms
8ZdN5i4.gif
https://leetcode.com/submissions/detail/806948593/
edit: đi từ 1000 -> 1000.1001 -> 1000.1001.1010.1011 cũng được mà đâu cần đi ngược
MjfezZB.png

edit: chỗ khởi tạo explicit Int(int value) : value{value} {} đáng lẽ phải có % kMod nhưng trong bài này ko có value nào >= kMod nên ko % cho lẹ, nếu ko có %kMod chỗ khởi tạo thì struct Int này ko reuse cho bài khác được

edit: sửa lại "nhân đôi", đi từ trái qua phải, chỉ cần operator<< và operator+
irGoYrZ.gif

https://leetcode.com/submissions/detail/806958957/

C++:
constexpr int kMod = 1'000'000'007;

struct Int {
    int value;
    explicit Int(int value) : value{value} {}
    Int& operator<<=(int bits) {
        for (long long k = 2; bits; bits >>= 1, k = k * k % kMod)
            if (bits % 2) value = value * k % kMod;
        return *this;
    }
    Int& operator+=(const Int& other) {
        value = (value + other.value) % kMod;
        return *this;
    }
    Int operator<<(int bits) const { return Int{*this} <<= bits; }
    Int operator+(const Int& other) const { return Int{*this} += other; }
};

struct Solution {
    Int concat_k_bits_nums(int k, int startIndex, int size) { // size must be power of 2: 1, 2, 4, 8, 16, ...
        auto s0 = Int{(1 << (k - 1)) + startIndex};
        auto add = Int{1};
        for (int sz = size, bits = k; sz >>= 1; add <<= 1, add += add << bits, bits *= 2) s0 += (s0 << bits) + add;
        return s0;
    }
    int concatenatedBinary(int n) {
        static constexpr int kLe[] = {0,         1,         27,        113015,    35297621,  144699479,
                                      590971296, 5842299,   313471718, 203851921, 396016205, 18825033,
                                      886701872, 950369496, 724626253, 351669993, 183542430};
        const int k = 32 - __builtin_clz(++n);
        auto res = Int{kLe[k - 1]};
        for (int m = 1 << k >> 1, startIndex = 0; m >>= 1;)
            if (n & m)
                res = (res << k * m) + concat_k_bits_nums(k, startIndex, m), startIndex += m;
        return res.value;
    }
};


edit: đi từ trái qua phải có phải logic hơn ko, như viết cái truth table
0
1
10
11
100
101
110
111
thì 10, 11 lấy từ 0, 1 thêm số 1 đằng trước. 100, 101, 110, 111 là 4 số 00, 01, 10, 11 thêm số 1 vào trước. Bác bribnt vẽ ra số so le ko hiểu gì
aVVa2xy.png


edit: https://leetcode.com/submissions/detail/807141722/ constexpr everything
cgE9MkI.gif

https://godbolt.org/z/eb8ah8ePv lang thượng đẳng ko cần magic năm bờ gì cả nhóe
JiZo9zf.png
JiZo9zf.png

Xem tệp đính kèm 1399386
Cách này thấy hại não vkl, đọc méo hiểu gì cả, =((
 
Cách này thấy hại não vkl, đọc méo hiểu gì cả, =((
ví dụ n=12 thì số cần tìm là "1101110010111011110001001101010111100", viết lại cho dễ thấy là
1_1011_100101110111_10001001101010111100
thì phần màu xanh có thể tính trong O(1) bằng cách tính trước, lưu vào mảng toy gọi là kLe, kLe[i] là số có số ghép lại từ các số có số bit <= (Less than or Equal to) i.
C++:
static constexpr int kLe[] = {0,         1,         27,        113015,    35297621,  144699479,
                              590971296, 5842299,   313471718, 203851921, 396016205, 18825033,
                              886701872, 950369496, 724626253, 351669993, 183542430};
ví dụ kLe[2] là 1_1011 hay là 11011 là 27

còn lại phải tính thằng lày 10001001101010111100 lẹ trong O(k^c) với k = logn là số bit của n, ở đây n là số nguyên 32 bit nên có tuy là O(k^c) nhưng k=32 là hằng số, c cũng là hằng số nên có thể coi là O(1)
FY7e6U1.png


viết tách ra cho dễ nhìn là 1000.1001.1010.1011.1100

bắt đầu từ sk[4,0,1]=1000 với s[k,i,len] là chuỗi nhị phân gộp từ các số có k bit, bắt đầu từ index i, có độ dài là len, có thể tính ra s[4,0,2]=1000.1001 (tạm gọi là s[0,2] cho ngắn) bằng cách gán tính add=0001, s' = s[0,1] + add, rồi tính được s[0,2] = (s4[0,1] << 4) + s':
add = 0001
s' = 1000 + 0001 = 1001
s[0,2] = 1000<<4 + 1001 = 1000.1001

tiếp theo tính được s[0,4] từ s[0,2] luôn:
add = 0010.0010
s' = 1000.1001 + 0010.0010 = 1010.1011
s[0,4] = 1000.1001<<8 + 1010.1011 = 1000.1001.1010.1011

s[0,4] lên s[0,8] tương tự

cái kinh quàng là s[4,1] có thể tính ra s[4,2], s[4,2] có thể tính ra s[4,4], i bất kì, ko nhất thiết i phải bắt đầu từ 1
cgE9MkI.gif
Tổng quát s[i,len] có thể tính ra s[i,2*len] trong O(1) (thật ra là O(k^c) với c > 2 vì dịch bit là k phép nhân, mà mỗi phép nhân là O(k^...) chứ ko ăn gian O(1), nhưng 1 lần nữa k là hằng số nên là O(1) thoy
JiZo9zf.png
). Như vậy chuỗi nhị phân có độ dài (k nhân) 2^x có thể tính lẹ trong x bước.

có cái tricky nữa là tính add.
bắt đầu từ add = 0001, tính add tiếp theo bằng cách tính add' = add << 1, rồi tính add = (add' << 4) + add' là được
add=0010.0010 thì tính ra 0100.0100.0100.0100 tương tự tính add' = add << 1 = 0100.0100, rồi dịch trái add; 8 bit (ko còn là 4 nữa) và cộng add' là ra next add = 0100.0100.0100.0100

vậy 1000.1001.1010.1011.1100 có thể tính lẹ 1000.1001.1010.1011 1100 là s[0,4] và s[4,1] rồi gộp lại bằng dịch trái và phép cộng, tất cả O(1)* hết
1hPyKvG.png


để tìm được số [0,4] và số [4,1] thì nhìn vào bit representation của n+1, bỏ bit cao nhứt đi:
n=12, --> n+1=13=1101. Số 1 đầu tiên tách ra được 100 là 4, bắt đầu từ i=0 --> s[0,4], cộng 4 cho i; số 0 tiếp theo bỏ qua; số 1 cuối cùng thì bắt đầu từ i=4 (i=0+4 trước đó) --> s[4,1]

code:
const int k = 32 - __builtin_clz(++n); đầu tiên tính số bit k của n+1. Thuật tón vẫn đúng nếu n từ k bit cộng 1 thành k+1 bit
rl1Kgfo.gif
vì số k+1 bit tiếp theo trừ số 1 cao nhứt ra còn lại toàn là 0 nên phần 2 ko có gì.
auto res = kLe.data[k - 1]; lấy giá trị phần đầu được precompute
for (int m = 1 << k >> 1, startIndex = 0; m >>= 1;) if (n & m) là để lấy bitmask ví dụ n=12 thì m chạy từ 100-->10-->1 để tìm các bit 1 trong n+1. Toy khởi tạo m=1000 vì check điều kiện dừng m bị >>= 1 đi còn 100 vào trong hàm for thì m đầu tiên sẽ là 100 chứ ko phải 1000. Giống như for (i = n; i-->0; thì i chạy từ n-1 xuống 0 vậy.
res = (res << k * m) + concat_k_bits_nums(k, startIndex, m), startIndex += m; thêm s[k,i,len] vào res. i ở đây là startIndex, m là len. Thật ra là 2 dòng nhưng toy code láo gộp dòng 2 vào bằng dấu phẩy , startIndex += m
hàm concat_k_bits_nums thì dễ đọc ròi
68747470733a2f2f692e696d6775722e636f6d2f6b493461396c482e6a7067
 
Sửa lần cuối:
1 dòng là đủ rồi, :p
Python:
    def pathSum(self, root: Optional[TreeNode], targetSum: int, curList = list()) -> List[List[int]]:
        return [] if not root \
                else [curList + [root.val]] if root.val == targetSum and not root.left and not root.right \
                else self.pathSum(root.left, targetSum - root.val, curList + [root.val]) \
                + self.pathSum(root.right, targetSum - root.val, curList + [root.val])
curList + [root.val] ko phải pop nhưng mà tạo ra hơi nhiều list đó fen ơi :v
mà sao leetcode nó cứ cho tham số kiểu lạc đà nhỉ, snake mới chuẩn phải ko nhỉ

Python:
class Solution:
    def pathSum(self, root: Optional[TreeNode], targetSum: int) -> List[List[int]]:
        arr = []
        def dfs(node, temp_arr):
            if not node:
                return
           
            temp_arr.append(node.val)

            if sum(temp_arr) == targetSum and not node.left and not node.right:
                arr.append(temp_arr[::])
                   
            dfs(node.left, temp_arr)
            dfs(node.right, temp_arr)
            temp_arr.pop()
       
        dfs(root, [])
       
        return arr
 
thì t cố tình tạo ra list mới mà. k tạo ra list mới thì nó đi nhánh khác rồi bị modified là bug liền.
Có cách để bớt tạo list mới là đi đến lá , nếu đúng thì mới quay lui lại tạo list, mà t lười viết kiểu đó, :D
là cách pop ra khỏi list chứ sao lại tạo list là sao fen, vẫn tạo list thì nó vẫn thêm bộ nhớ mà
 
thì t cố tình tạo ra list mới mà. k tạo ra list mới thì nó đi nhánh khác rồi bị modified là bug liền.
Có cách để bớt tạo list mới là đi đến lá , nếu đúng thì mới quay lui lại tạo list, mà t lười viết kiểu đó, :D
cơ mà cách tạo list ở node lá thì độ phức tạp worst case là

NLogNLogN à fen, vì N/2 là số lượng nút lá, LogN số mỗi path, vậy có tổng cộng NlogN rồi, và đến nút lá lại dùng lệnh curLIst[::] thì nó lại đi qua LogN (là số lượng có trong list) thì tổng cộng là NlogNlogN
@@

còn cách tạo list mới cho mỗi tham số của fen làm thì tui ko biết cách tính độ phức tạp, nếu mà cộng list chỉ là O(1)
thì độ phức tạp của fen vẫn là NlogN thui
 
ví dụ n=12 thì số cần tìm là "1101110010111011110001001101010111100", viết lại cho dễ thấy là
1_1011_100101110111_10001001101010111100
thì phần màu xanh có thể tính trong O(1) bằng cách tính trước, lưu vào mảng toy gọi là kLe, kLe[i] là số có số ghép lại từ các số có số bit <= (Less than or Equal to) i.
C++:
static constexpr int kLe[] = {0,         1,         27,        113015,    35297621,  144699479,
                              590971296, 5842299,   313471718, 203851921, 396016205, 18825033,
                              886701872, 950369496, 724626253, 351669993, 183542430};
ví dụ kLe[2] là 1_1011 hay là 11011 là 27

còn lại phải tính thằng lày 10001001101010111100 lẹ trong O(k^c) với k = logn là số bit của n, ở đây n là số nguyên 32 bit nên có tuy là O(k^c) nhưng k=32 là hằng số, c cũng là hằng số nên có thể coi là O(1)
FY7e6U1.png


viết tách ra cho dễ nhìn là 1000.1001.1010.1011.1100

bắt đầu từ sk[4,0,1]=1000 với s[k,i,len] là chuỗi nhị phân gộp từ các số có k bit, bắt đầu từ index i, có độ dài là len, có thể tính ra s[4,0,2]=1000.1001 (tạm gọi là s[0,2] cho ngắn) bằng cách gán tính add=0001, s' = s[0,1] + add, rồi tính được s[0,2] = (s4[0,1] << 4) + s':
add = 0001
s' = 1000 + 0001 = 1001
s[0,2] = 1000<<4 + 1001 = 1000.1001

tiếp theo tính được s[0,4] từ s[0,2] luôn:
add = 0010.0010
s' = 1000.1001 + 0010.0010 = 1010.1011
s[0,4] = 1000.1001<<8 + 1010.1011 = 1000.1001.1010.1011

s[0,4] lên s[0,8] tương tự

cái kinh quàng là s[4,1] có thể tính ra s[4,2], s[4,2] có thể tính ra s[4,4], i bất kì, ko nhất thiết i phải bắt đầu từ 1
cgE9MkI.gif
Tổng quát s[i,len] có thể tính ra s[i,2*len] trong O(1) (thật ra là O(k^c) với c > 2 vì dịch bit là k phép nhân, mà mỗi phép nhân là O(k^...) chứ ko ăn gian O(1), nhưng 1 lần nữa k là hằng số nên là O(1) thoy
JiZo9zf.png
). Như vậy chuỗi nhị phân có độ dài (k nhân) 2^x có thể tính lẹ trong x bước.

có cái tricky nữa là tính add.
bắt đầu từ add = 0001, tính add tiếp theo bằng cách tính add' = add << 1, rồi tính add = (add' << 4) + add' là được
add=0010.0010 thì tính ra 0100.0100.0100.0100 tương tự tính add' = add << 1 = 0100.0100, rồi dịch trái add; 8 bit (ko còn là 4 nữa) và cộng add' là ra next add = 0100.0100.0100.0100

vậy 1000.1001.1010.1011.1100 có thể tính lẹ 1000.1001.1010.1011 1100 là s[0,4] và s[4,1] rồi gộp lại bằng dịch trái và phép cộng, tất cả O(1)* hết
1hPyKvG.png


để tìm được số [0,4] và số [4,1] thì nhìn vào bit representation của n+1, bỏ bit cao nhứt đi:
n=12, --> n+1=13=1101. Số 1 đầu tiên tách ra được 100 là 4, bắt đầu từ i=0 --> s[0,4], cộng 4 cho i; số 0 tiếp theo bỏ qua; số 1 cuối cùng thì bắt đầu từ i=4 (i=0+4 trước đó) --> s[4,1]

code:
const int k = 32 - __builtin_clz(++n); đầu tiên tính số bit k của n+1. Thuật tón vẫn đúng nếu n từ k bit cộng 1 thành k+1 bit
rl1Kgfo.gif
vì số k+1 bit tiếp theo trừ số 1 cao nhứt ra còn lại toàn là 0 nên phần 2 ko có gì.
auto res = kLe.data[k - 1]; lấy giá trị phần đầu được precompute
for (int m = 1 << k >> 1, startIndex = 0; m >>= 1;) if (n & m) là để lấy bitmask ví dụ n=12 thì m chạy từ 100-->10-->1 để tìm các bit 1 trong n+1. Toy khởi tạo m=1000 vì check điều kiện dừng m bị >>= 1 đi còn 100 vào trong hàm for thì m đầu tiên sẽ là 100 chứ ko phải 1000. Giống như for (i = n; i-->0; thì i chạy từ n-1 xuống 0 vậy.
res = (res << k * m) + concat_k_bits_nums(k, startIndex, m), startIndex += m; thêm s[k,i,len] vào res. i ở đây là startIndex, m là len. Thật ra là 2 dòng nhưng toy code láo gộp dòng 2 vào bằng dấu phẩy , startIndex += m
hàm concat_k_bits_nums thì dễ đọc ròi
68747470733a2f2f692e696d6775722e636f6d2f6b493461396c482e6a7067
Đọc nghiền ngẫm mãi cuối cùng cũng hiểu được ý tưởng. :)

Cái đoạn từ s[i, len] tính ra được s[i,len*2] trong O(1) thấy hack vkl. :ROFLMAO:
 
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.727
Quay lại
Lên đầu trang