[Leetcode] Weekly Contest 241 후기 및 풀이
in Problem Solving on Leetcode
Contest : Leetcode - Weekly Contest 241
Solution code : Github
Weekly Contest 241 후기
3문제를 PASS 했습니다. 3번 문제가 평소보다도 난이도가 낮았는데도 3번을 못 풀고 4번을 통과했습니다. 끝나자마자 다시 생각하니 3번도 바로 풀렸습니다. 왜 풀이를 바로 못 떠올렸는지 이해가 안될 정도로 너무 아쉬웠습니다. 최근에 콘테스트를 못했는데 그 영향이 있었나봅니다. 다행히 4번 문제는 어려운 편이었는데도 풀어서 랭킹은 처음으로 500등대를 찍게 됐습니다.
1번 문제는 단순 Brute Force 문제였는데 콘테스트 끝난 후 풀이를 보니 XOR 특성을 이용해서 O(N)을 가능한 방법이 있어서 배울 점이 있었습니다. 2번 문제는 아이디어만 있으면 Greedy하게 풀 수 있었고 3번 문제는 (Key, value)를 저장하는 Hashmap으로 풀었습니다. 4번 문제는 DP 문제였는데 관계식을 찾는 것이 쉽지 않았습니다. 다행히 이전에 비슷한 문제를 풀어봐서 풀이를 찾을 수 있었던 것 같습니다.
문제 1. Sum of All Subset XOR Totals
문제
정수형 배열 nums
가 주어집니다. nums
에서 가능한 모든 부분집합의 XOR total
들의 합을 구하는 문제입니다.
XOR total
은 배열의 모든 정수를 XOR
한 값을 의미합니다. 또한 부분집합은 동일한 값의 정수를 여러 개 포함할 수 있습니다.
제약 조건:
1 <= nums.length <= 12
1 <= nums[i] <= 20
풀이
nums
의 길이가 n
이라고 할 때 모든 수를 비트열로 변경하여 각각의 비트에 해당하는 값 2^(n-1)
, 2^(n-2)
, … , 2^1
, 2^0
을 몇개씩 더해야 하는지로 생각해봅시다.
부분집합은 모든 정수를 한번씩 포함하거나 포함하지 않거나로 정확히 반으로 나눌 수 있습니다. 이 때 XOR
연산의 특성상 모든 수의 첫번째 비트열에서 1
이 하나라도 있는 경우에는 첫번째 비트열의 값 2^(n-1)
을 2^(n-1)
번 더한 것과 같습니다. 만약 2번째 비트열의 모든 값이 0
이라면 2^(n-2)
값은 한번도 더하지 않습니다. 모든 부분집합에서 해당 비트열은 0
이기 때문입니다.
따라서 비트열에 1
이 존재하는 지 여부는 OR
연산으로 확인할 수 있고, 각각의 비트열 값에 2^(n-1)
을 곱한 값을 구하면 됩니다.
더 쉬운 방법으로 모든 부분집합을 구한 후에 모든 부분집합의
XOR Total
값을 구하는 것도 가능합니다. 모든 부분집합은0
~(2^n)-1
값을 각각 비트열을 집합으로 취급하면 쉽게 구할 수 있습니다. 이 방법은 O(2^N * N) 입니다.
- Time : O(N) [N:
nums.length
] - Space : O(1)
- Tag :
Bit Manipulation
C++
class Solution {
public:
int subsetXORSum(vector<int>& nums) {
int ans = 0;
for(int i=0; i<nums.size(); i++)
ans |= nums[i];
return ans * pow(2, nums.size()-1);
}
};
Python
class Solution:
def subsetXORSum(self, nums: List[int]) -> int:
ans = 0
for i in nums:
ans |= i
return ans * 2**(len(nums) - 1)
문제 2. Minimum Number of Swaps to Make the Binary String Alternating
문제
0
과 1
로 이루어진 문자열 s
가 주어집니다. alternating
문자열을 만들기 위한 문자의 최소 교환횟수를 구하는 문제입니다.
alternating
문자열은 인접한 두개의 문자가 서로 다른 문자인 경우를 말합니다. 만약 만들 수 없으면 -1
을 출력합니다.
제약 조건:
1 <= s.length <= 1000
s[i] is either '0' or '1'.
풀이
먼저 길이가 n
인 alternating
문자열은 1
로 시작하는 것과 0
으로 시작하는 것으로 2가지 문자열이 전부입니다.
문자열 s
를 1
로 시작하는 alternating
문자열로 만들기 위해서는 1
인 자리인 곳에 0
인 개수와 0
인 자리에 1
의 개수가 같아야 합니다. 그렇지 않으면 만들지 못합니다. 또한 이 개수가 최소 교환 횟수가 됩니다.
마찬가지 방법으로 0
으로 시작하는 alternating
문자열을 만드는 최소 교환횟수를 구할 수 있습니다. 둘 중 작은 값이 답이 되며, 둘다 만들 수 없으면 -1
입니다.
- Time : O(N) [N :
s.length
] - Space : O(1)
- Tag :
Greedy
C++
class Solution {
public:
int minSwaps(string s) {
int n1 = 0, n2 = 0;
int ans = 1e9;
for (int i = 0; i < s.size(); i += 2) if (s[i] == '0') n1++;
for (int i = 1; i < s.size(); i += 2) if (s[i] == '1') n2++;
if (n1 == n2) ans = min(ans, n1);
n1 = 0, n2 = 0;
for (int i = 0; i < s.size(); i += 2) if (s[i] == '1') n1++;
for (int i = 1; i < s.size(); i += 2) if (s[i] == '0') n2++;
if (n1 == n2) ans = min(ans, n1);
return ans == 1e9 ? -1 : ans;
}
};
Python
class Solution:
def minSwaps(self, s: str) -> int:
n1 = sum([1 if s[i] == '0' else 0 for i in range(0, len(s), 2)])
n2 = sum([1 if s[i] == '1' else 0 for i in range(1, len(s), 2)])
ans1 = n1 if (n1 == n2) else 1e9
n1 = sum([1 if s[i] == '1' else 0 for i in range(0, len(s), 2)])
n2 = sum([1 if s[i] == '0' else 0 for i in range(1, len(s), 2)])
ans2 = n1 if (n1 == n2) else 1e9
if ans1 == 1e9 and ans2 == 1e9:
return -1
return min(ans1, ans2)
문제 3. Finding Pairs With a Certain Sum
문제
정수형 배열 nums1
과 nums2
가 주어집니다. 아래와 같은 2가지 연산을 구현하는 문제입니다.
void Add(int index, int val)
:nums2[index]
의 값에val
을 더한다.int count(int tot)
:nums1[i] + nums2[j] == tot
인(i, j)
의 쌍의 개수를 반환한다.
제약 조건:
1 <= nums1.length <= 1000
1 <= nums2.length <= 10^5
1 <= nums1[i] <= 10^9
1 <= nums2[i] <= 10^5
0 <= index < nums2.length
1 <= val <= 10^5
1 <= tot <= 10^9
add
와count
는 각각 최대 1000번 호출
풀이
nums2
의 각 정수의 값을 key
로 하고 해당 key
와 동일한 값을 가진 숫자의 개수를 value
로 하는 해시테이블을 구성합니다.
Add
연산은 이전 값의 개수를 -1
하고, 추가되어 새로 생성된 값을 key
로 하는 개수를 +1
합니다.
Count
연산은 tot - nums1[i] == nums2[j]
값을 찾아야 하므로 tot - nums1[i]
의 개수가 몇개인지 구하면 됩니다.
- Time [N :
num1.length
, M:nums2.length
]- Constructor : O(M)
- Add : O(1000*1)
- Count : O(1000*N)
- Space : O(N + M)
- Tag :
Hashmap
해시테이블의 연산은 충돌이 거의 없다는 가정하에 O(1)로 계산했습니다.
C++
class FindSumPairs {
public:
vector<int> n1;
vector<int> n2;
unordered_map<int, int> m2;
FindSumPairs(vector<int>& nums1, vector<int>& nums2) {
n1 = nums1;
n2 = nums2;
for (int i = 0; i < n2.size(); i++)
m2[n2[i]]++;
}
void add(int index, int val) {
m2[n2[index]]--;
n2[index] += val;
m2[n2[index]]++;
}
int count(int tot) {
int ans = 0;
for (int i = 0; i < n1.size(); i++)
{
int target = tot - n1[i];
ans += m2[target];
}
return ans;
}
};
Python
class FindSumPairs:
def __init__(self, nums1: List[int], nums2: List[int]):
self.n1 = nums1
self.n2 = nums2
self.m = {}
for x in nums2:
if x in self.m:
self.m[x] +=1
else:
self.m[x] = 1
def add(self, index: int, val: int) -> None:
self.m[self.n2[index]] -=1
self.n2[index] += val
if self.n2[index] in self.m:
self.m[self.n2[index]] +=1
else:
self.m[self.n2[index]] = 1
def count(self, tot: int) -> int:
return sum([self.m[tot-x] if tot-x in self.m else 0 for x in self.n1])
문제 4. Number of Ways to Rearrange Sticks With K Sticks Visible
문제
1
~n
까지 서로 다른 크기를 n
개의 가진 막대기가 있습니다. 막대기를 왼쪽부터 차례대로 세운 후에 왼쪽의 막대기가 높은 경우 오른쪽의 막대기는 보이지 않습니다. 이 때 왼쪽에서 정확히 k
개의 막대기만 보이게 세우는 경우의 수를 구하는 문제입니다.
n
과 k
가 주어졌을 때, 위의 조건에 맞는 경우의 수를 구하고 만약 큰 값인 경우 modulo 값인 1e9 + 7
로 나눈 나머지를 구하면 됩니다.
예를 들어, 막대기가 순서대로
[1, 3, 2, 5, 4]
로 놓여져 있다면 왼쪽부터 봤을 때[1, 3, 5]
로 3가지 막대기만 보이게 됩니다. 이것은n=5, k=3
인 경우의 수 중 하나입니다.
제약 조건:
1 <= n <= 1000
1 <= k <= n
풀이
먼저 n
과 k
가 주어졌을때 경우의 수가 d[n][k]
이라고 가정해봅시다. 그러면 경우의 수를 1
막대기가 1번째있는 경우, 2번째있는 경우, … , n번째 있는 경우의 합으로 표현할 수 있습니다.
n
개의 막대기 길이를 모두 1
을 더해도 경우의 수는 같습니다. 따라서 경우의 수를 쉽게 계산하기 위해 n-1
개의 막대기를 세운 후에 1
을 증가시키고, 1
크기를 가진 막대기를 어떤 곳에 배치해야 할지로 생각해보겠습니다.
1
막대기를 1번째에 배치한 경우의 수는 d[n-1][k-1]
입니다. 그 이유는 n-1
개의 막대기를 왼쪽에서 k-1
개만 보이도록 세운 후에 막대기의 높이를 1
씩 높인 다음 1
막대기를 가장 왼쪽에 배치한 것과 같기 때문입니다.
마찬가지로 1
막대기를 2번째에 배치한 경우의 수는 d[n-1][k]
입니다. 2
~n
막대기를 세운 후 2번째에 1
을 배치한 것과 같기 때문입니다. 1
막대기를 3번째 ~ n번째 배치한 것도 모두 d[n-1][k]
와 같습니다.
따라서 다음과 같은 Dynamic Programming
으로 경우의 수를 구할 수 있습니다.
d[n][k] : n개의 막대기를 왼쪽에서 k개만 보이게 세우는 경우의 수
d[n][k] = d[n-1][k-1] + d[n-1][k] * (n-1)
- Time : O(N*K) [N :
n
, K:k
] - Space : O(N*K)
- Tag :
DP
C++
class Solution {
public:
#define MOD 1000000007ll
long long d[1001][1001];
int rearrangeSticks(int n, int k) {
d[1][1] = 1;
for (int i = 2; i <= n; i++)
for (int j = 1; j <= k; j++)
d[i][j] = (d[i - 1][j - 1] + d[i - 1][j] * (long long)(i - 1)) % MOD;
return d[n][k];
}
};
Python
class Solution:
def rearrangeSticks(self, n: int, k: int) -> int:
mod = 1000000007
d = [[0]*1001 for i in range(1001)]
d[1][1] = 1
for i in range(2, n+1):
for j in range(1, k+1):
d[i][j] = (d[i-1][j-1] + d[i-1][j]*(i-1)) % mod
return d[n][k]