leetcode菜鸡啃题
1 只出现一次数字
给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。
菜狗思路
搞个set,如果已经存在删掉,不存在就加进去,最后剩下的就是单独的那个
public int singleNumber(int[] nums) {
HashSet<Integer> set = new HashSet<>();
for(int i = 0; i < nums.length; i++) {
if (set.contains(nums[i])) {
set.remove(nums[i]);
} else {
set.add(nums[i]);
}
}
return set.iterator().next().intValue();
}
大佬思路
使用位运算的异或
自己与自己异或为0,任何数与0异或的自己,异或满足交换律和集合律,所以直接对整个数组进行异或,即可得到最终的结果
public int singleNumber(int[] nums) {
int single = 0;
for (int num : nums) {
single ^= num;
}
return single;
}
2 多数元素
给定一个大小为 n
的数组 nums
,返回其中的多数元素。多数元素是指在数组中出现次数 大于 ⌊ n/2 ⌋
的元素。
菜狗思路
仍然只能想到用hashMap,记录次数,然后找记录最多的那个数
大佬思路
1、直接排序,找下标大于等于n/2
的数即可,众数的下标总大于等于n/2
2、分治算法,整个数组的众数一定是左右两边的众数中的一个,所以可以不断分支,知道子数组长度为一,一定是众数,之后回溯的时候,只需要判断左右两边的众数那个居多即可,时间O*(nlogn),空间O(logn)。
class Solution {
private int countInRange(int[] nums, int num, int lo, int hi) {
int count = 0;
for (int i = lo; i <= hi; i++) {
if (nums[i] == num) {
count++;
}
}
return count;
}
private int majorityElementRec(int[] nums, int lo, int hi) {
if (lo == hi) {
return nums[lo];
}
int mid = (hi - lo) / 2 + lo;
int left = majorityElementRec(nums, lo, mid);
int right = majorityElementRec(nums, mid + 1, hi);
if (left == right) {
return left;
}
int leftCount = countInRange(nums, left, lo, hi);
int rightCount = countInRange(nums, right, lo, hi);
return leftCount > rightCount ? left : right;
}
public int majorityElement(int[] nums) {
return majorityElementRec(nums, 0, nums.length - 1);
}
}
3、Boyer-Moore 投票算法,只能说很牛逼,线性时间,常数空间
维护一个候选众数 candidate 和它出现的次数 count。初始时 candidate 可以为任意值,count 为 0;
我们遍历数组 nums 中的所有元素,对于每个元素 x,在判断 x 之前,如果 count 的值为 0,我们先将 x 的值赋予 candidate,随后我们判断 x:
如果 x 与 candidate 相等,那么计数器 count 的值增加 1;
如果 x 与 candidate 不等,那么计数器 count 的值减少 1。
在遍历完成后,candidate 即为整个数组的众数。
public static int majorityElement(int[] nums) {
int candidate = 0, count = 0;
for (int num : nums) {
if (count == 0) {
candidate = num;
count++;
} else {
if (num == candidate) {
count++;
} else {
count--;
}
}
}
return candidate;
}
3 三数之和-梦破碎的地方
给你一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?请你找出所有和为 0 且不重复的三元组。
菜鸡做法:
先排序,然后用双重循环,其中使用hashMap和hashSet辅助查找和判断是否重复,老复杂了,需要处理的细节很多
大佬做法
使用双指针,再第二重循环的时候使用双指针,从两头往中间靠,防止重复
public List<List<Integer>> threeSum(int[] nums) {
List<List<Integer>> res = new ArrayList<>();
if (nums == null || nums.length < 3) return res;
Arrays.sort(nums);
int len = nums.length;
// 确定一个然后双指针
for (int i = 0; i < len - 2; i++) {
// 剪枝
if(nums[i] > 0) break;
// 对nums[i]进行去重
if(i > 0 && nums[i] == nums[i - 1]) continue;
int l = i + 1, r = len - 1;
while (l < r) {
// 分为三类,每次根据sum与0关系移动l或者r一次
int sum = nums[i] + nums[l] + nums[r];
// sum太小:左指针右移使得变大
if (sum < 0) l++;
else if(sum > 0) r--;
else {
// 加入结果
res.add(Arrays.asList(nums[i], nums[l], nums[r]));
// 去重:l与r均需移动到跟当前位不一样的数字上
int tmpL = nums[l], tmpR = nums[r];
while (l < r && nums[l] == tmpL) l++;
while (l < r && nums[r] == tmpR) r--;
}
}
}
return res;
}
下面还是使用c++语言
4 第K大整数
给你一个字符串数组 nums 和一个整数 k 。nums 中的每个字符串都表示一个不含前导零的整数。
返回 nums 中表示第 k 大整数的字符串。
注意:重复的数字在统计时会视为不同元素考虑。例如,如果 nums 是 ["1","2","2"],那么 "2" 是最大的整数,"2" 是第二大的整数,"1" 是第三大的整数。
这里考察自定义排序规则
string kthLargestNumber(vector<string>& nums, int k) {
// 自定义比较函数,在 s1 对应的整数较大时返回 true,反之返回 false
auto cmp = [](const string& s1, const string& s2) -> bool{
// 首先比较字符串长度
if (s1.size() > s2.size()){
return true;
}
else if (s1.size() < s2.size()){
return false;
}
else{
// 长度相等时比较字符串字典序大小
return s1 > s2;
}
};
sort(nums.begin(), nums.end(), cmp);
return nums[k-1];
}
但是我感觉这样直接排序时间复杂度是不是太大了,可以先进行一部分删选然后在排序
string kthLargestNumber(vector<string>& nums, int k) {
// 先确定他在哪个长度区间,然后在进行排序
int a[100] = {0};
for(int i=0; i<nums.size(); i++) {
a[nums[i].length() - 1]++;
}
int j=99;
for(; j>=0; j--) {
if(k > a[j])
k = k - a[j];
else
break;
}
for(vector<string>::iterator iter=nums.begin(); iter != nums.end(); iter++) {
if((*iter).length() != j+1) {
nums.erase(iter);
iter--;
}
}
sort(nums.begin(), nums.end());
return nums[nums.size() - k];
}
5 1比特与2比特字符
有两种特殊字符:
第一种字符可以用一比特 0 表示 第二种字符可以用两比特(10 或 11)表示 给你一个以 0 结尾的二进制数组 bits ,如果最后一个字符必须是一个一比特字符,则返回 true 。
这个比较简单,只需要正向遍历数组(注意终止条件),如果是1开头就前进两位,否则前进一位,只需要注意一下最后停的位置是不是数组末端
bool isOneBitCharacter(vector<int>& bits) {
int i = 0;
for(; i<bits.size() - 1; i++) {
if(bits[i] == 1) {
i+=1;
}
}
return i == bits.size() - 1;
}
6 二叉搜索树中的中序后继
给定一棵二叉搜索树和其中的一个节点p,找到该节点在树中的中序后继。如果节点没有中序后继,请返回null。
节点 p 的后继是值比 p.val 大的节点中键值最小的节点,即按中序遍历的顺序节点 p 的下一个节点。
做法一,就按照中序遍历方式,记录节点p出现的位置,找到该位置的下一个节点即可
TreeNode* inorderSuccessor(TreeNode* root, TreeNode* p) {
stack<TreeNode*> tree;
set<int> value;
tree.push(root);
value.insert(root->val); // 记录是否访问过
TreeNode* temp;
bool prev = false;
while(!tree.empty()) {
temp = tree.top();
if(temp->left != NULL && value.find(temp->val) == value.end()) {
tree.push(temp->left);
} else {
if(prev) {
return temp;
}
value.insert(temp->val);
tree.pop();
if(temp->val == p->val) {
prev = true;
}
if(temp->right != NULL) {
tree.push(temp->right);
}
}
}
return NULL;
}
// 自己写的代码,还需要有一个额外的set来记录是否访问,官方给的就用了一种方法,奇妙的解决了这种问题
TreeNode* inorderSuccessor(TreeNode* root, TreeNode* p) {
stack<TreeNode*> st;
TreeNode *prev = nullptr, *curr = root;
while (!st.empty() || curr != nullptr) {
while (curr != nullptr) {
st.emplace(curr);
curr = curr->left;
}
curr = st.top();
st.pop();
if (prev == p) {
return curr;
}
prev = curr;
curr = curr->right; // 每次直接将当前节点赋值为右子节点,然后先循环,z取top
}
return nullptr;
}
// 人傻了,中序遍历不需要记录是否已访问,菜狗
void inOrder(BiTree T) {
InitStack(S);
BiTree p = T;
while(p || !IsEmpty(S)) {
if(p) {
Push(S, p);
p = p->lchild;
} else {
Pop(S, p);
visit(p);
p = p->rchild;
}
}
}
/*
1.沿着根的左孩子,依次入栈,直到左孩子为空,说明已找到可以输出的结点
2.栈顶元素出栈并访问:若其右孩子为空,继续执行2;若其右孩子不空,将右子树转执行1
*/
做法二,利用二叉搜索树的性质
如果他有右子树,就找右子树的最左孩子
如果没有右子树,就利用二叉搜索树的性质,后继节点一定是值比他大的节点,或者为NULL,
所以不断的二分比较就行了,知道比较到空节点,这里需要注意的是,只有节点大于的时候,才保存这个successor(这里好难理解,菜狗),其实就是记录大于他的节点中的最小的哪个,所以才需要只有大于的时候才更新节点。
TreeNode* inorderSuccessor(TreeNode* root, TreeNode* p) {
TreeNode *successor = nullptr;
if (p->right != nullptr) {
successor = p->right;
while (successor->left != nullptr) {
successor = successor->left;
}
return successor;
}
TreeNode *node = root;
while (node != nullptr) {
if (node->val > p->val) {
successor = node;
node = node->left;
} else {
node = node->right;
}
}
return successor;
}
7 第N位数字
给你一个整数 n
,请你在无限的整数序列 [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, ...]
中找出并返回第 n
位上的数字。比如11,结果是0。
做法
先找到他是所属的位置是多少位的,然后再进一步细分它属于哪个数字,然后再提取出对应的位置的数字
一位数字1-9 共9个,二位数字10-99共99个,三位数字100-999共900个,于是可以找到规律,不断地进行减法,便可以确定他所属的位置是多少位的,也就是处于那一组。
然后再对位数作除法,便可以知道他是这一组中的第几个数字
最后就是简单的提取某一位的数字
int findNthDigit(int n) {
// int型不够
long x = 1, curr = 9, pre = 0, pos = 9;
if(n <= 9) {
return n;
}
// 不断做减法,直到到达瓶颈
while(n > curr) {
x++;
pre = curr;
pos = pos * 10;
curr = pos * x;
n -= pre;
}
// 确定第几个数字
int div, res, result;
div = n / x - 1;
res = n % x;
div += res == 0 ? pow(10, x - 1) : 1 + pow(10, x - 1);
res = res == 0 ? res + 1 : x - res + 1;
// 提取数字
while(res >= 1) {
result = div % 10;
div /= 10;
res--;
}
return result;
}