Leetcode
概览
具体题目(部分)
1584. 连接所有点的最小费用
题目描述:
代码:
class Solution(object):
def minCostConnectPoints(self, points):
Edges = []
mydict = {} # 用来存储哪些点已经在图中了
m = len(points)
for i in range(m-1):
for j in range(i+1, m):
pot1 = points[i]
pot2 = points[j]
absdis = abs(pot1[0] - pot2[0]) + abs(pot1[1] - pot2[1])
Edges.append([absdis, i ,j])
def takeFirst(elem):
return elem[0]
Edges.sort(key=takeFirst)
edges = 0
flag = 0
alldistance = 0
while edges < m-1:
find = 0
val = []
for item in Edges:
if flag == 0:
alldistance += item[0]
mydict[item[1]] = 1
mydict[item[2]] = 1
flag = 1
edges += 1
find = 1
val = item
break
else:
i = item[1]
j = item[2]
if mydict.get(i,'no') == 'no':
haveI = False
else:
haveI = True
if mydict.get(j,'no') == 'no':
haveJ = False
else:
haveJ = True
if haveI == True and haveJ == True:
continue
if haveI == False and haveJ == False:
continue
mydict[i] = 1
mydict[j] = 1
alldistance += item[0]
val = item
edges += 1
break
Edges.remove(val)
return alldistance
思路:构造一个无向图G,输入的每个点都是G的结点,各个点之间的曼哈顿距离就是边的度,原问题被转化成一个求最小生成树的问题,可以用prim算法解决。
提交结果:
215. 数组中的第K个最大元素
题目描述:
代码:
class Solution(object):
def findKthLargest(self, nums, k):
while k>1:
maxN = max(nums)
nums.remove(maxN)
k = k - 1
return max(nums)
思路:每次找到最大的删除掉,执行k-1次, 然后返回数组中的最大值。
提交结果:
275. H 指数 II
题目描述:
代码:
class Solution(object):
def hIndex(self, citations):
m = len(citations) - 1
if m == -1:
return 0
if citations == [0]:
return 0
if m == 0 and citations[0] != 0:
return 1
if max(citations) == 0:
return 0
flag = 0
while m >= 0:
if citations[m] > flag:
flag += 1
m -= 1
else:
return flag
return flag
思路:因为输入已经是升序排序的了,让H指数用变量flag来代替,初始值为0。从数组的末端往前遍历,每执行一次flag增加1,当flag不再满足小于当前所遍历到的数组值的时候,就说明它被引用flag次的论文不多于flag篇。(本题要注意一些边界条件)
提交结果:
279 完全平方数
题目描述:
代码:
class Solution(object):
def numSquares(self, n):
if n==1:
return 1
dishu = 1
ans = [i for i in range(n+1)]
for i in range(1,n+1):
j = 1
while i - j*j >= 0:
ans[i] = min(ans[i], ans[i-j*j]+1)
j+=1
return ans[-1]
思路:
动态规划,重点是要找准状态转移方程:ans[i] = min(ans[i], ans[i-j*j]+1)
提交结果:
150 逆波兰表达式求值
题目描述:
代码:
class Solution(object):
def evalRPN(self, tokens):
"""
:type tokens: List[str]
:rtype: int
"""
stackOfNum = []
for item in tokens:
# print(stackOfNum)
if item == '+' or item =='-' or item=='*' or item == '/':
num1 = int(stackOfNum[-2])
num2 = int(stackOfNum[-1])
if item == '+':
newNum = num1 + num2
elif item == '-':
newNum = num1-num2
elif item == '*':
newNum = num2 * num1
else:
# newNum =int( num1 / num2 )
if (num1 <= 0 and num2 >= 0) or (num1>=0 and num2<=0):
newNum = -(abs(num1) // abs(num2))
else:
newNum = num1 // num2
del stackOfNum[-1]
del stackOfNum[-1]
stackOfNum.append(str(newNum))
else:
stackOfNum.append(item)
# print(stackOfNum)
return int(stackOfNum[-1])
思路:
用一个stack来辅助操作,当输入是数字则入栈,当输入是运算符号,则让栈尾的两个元素参与运算,然后把这两个元素出栈,让运算结果入栈,完成所有运算后,最终结果就保存在栈顶。(要注意的是除法操作时,对于除数和被除数异号的情况单独讨论)
提交结果:
299 猜数字游戏
题目描述:
代码:
class Solution(object):
def getHint(self, secret, guess):
"""
:type secret: str
:type guess: str
:rtype: str
"""
def findBulls(secret, guess):
flag = 0
str1 = ''
str2 = ''
for i in range(len(secret)):
if secret[i] == guess[i]:
flag += 1
else:
str1 += secret[i]
str2 += guess[i]
return flag , str1 ,str2
bulls , sec, gue = findBulls(secret, guess)
mydice = {}
cows = 0
for item in sec:
if mydice.get(item,'no') == 'no':
mydice[item] = 1
else :
mydice[item] += 1
for i in gue:
if mydice.get(i, 'no') != 'no':
if mydice[i] >= 1:
cows += 1
mydice[i] -= 1
return ''+str(bulls) + 'A' + str(cows) + 'B'
思路:
将题目分解为两部分,1、找公牛,2、找奶牛。
1、构造找公牛的函数,按位逐次比较即可找出公牛数量。同时,该函数还返回秘密数字和猜测值的不同部分。便于找奶牛。
2、分别遍历密文和guess,利用字典来找奶牛数量。
提交结果:
17 电话号码的字母组合
题目描述:
代码:
class Solution(object):
def letterCombinations(self, digits):
"""
:type digits: str
:rtype: List[str]
"""
if digits == '':
return []
mydict = {
'2':['a','b','c'],
'3':['d','e','f'],
'4':['g','h','i'],
'5':['j','k','l'],
'6':['m','n','o'],
'7':['p','q','r','s'],
'8':['t','u','v'],
'9':['w','x','y','z']
}
# ans = []
ans = mydict[digits[0]]
for i in range(1, len(digits)):
newans = []
nowC = mydict[digits[i]]
nowS = ''
for item in nowC:
for items in ans:
nowS = items + item
newans.append(nowS)
ans = newans
return ans
思路:
1、用字典存储电话号码对应的字母
2、暴力迭代
3、注意每完成一次外层循环更新一下ans
提交结果:
1631 最小体力消耗路径
题目描述:
代码:
/**
* @param {number[][]} heights
* @return {number}
*/
var minimumEffortPath = function(heights) {
const m = heights.length;
const n = heights[0].length;
const edges = [];
for (let i = 0; i < m; ++i) {
for (let j = 0; j < n; ++j) {
const id = i * n + j;
if (i > 0) {
edges.push([id - n, id, Math.abs(heights[i][j] - heights[i - 1][j])]);
}
if (j > 0) {
edges.push([id - 1, id, Math.abs(heights[i][j] - heights[i][j - 1])]);
}
}
}
// 按照list的第三项做排序(升序)
// 原本的每个空格heights[i][j] 是被作为了图G中的一个结点,结点编号为0 - (m*n -1)
edges.sort((a, b) => a[2] - b[2]);
console.log(edges)
// new 一个并查集
const uf = new UnionFind(m * n);
let ans = 0;
// 按照权重从小到大挨个往并查集里面并入。
// 等到第0和结点和最后一个结点联通,此时的权重就是最小 体力消耗值
for (const edge of edges) {
const x = edge[0], y = edge[1], v = edge[2];
uf.unite(x, y);
if (uf.connected(0, m * n - 1)) {
ans = v;
break;
}
}
return ans;
};
// JS版本的 并查集模板
class UnionFind {
// 构造函数
constructor (n) {
this.parent = new Array(n).fill(0).map((element, index) => index);
this.size = new Array(n).fill(1);
// 当前连通分量数目
this.setCount = n;
}
// 找parent
findset (x) {
if (this.parent[x] === x) {
return x;
}
this.parent[x] = this.findset(this.parent[x]);
return this.parent[x];
}
// 将两个子集联合起来, (联通起来)
unite (a, b) {
// 找这两个子集parent, 如果parent相同,则本身就是联通的,返回false
let x = this.findset(a), y = this.findset(b);
if (x === y) {
return false;
}
// 让小的为y, 大的为x, 保证是小的并入大的
if (this.size[x] < this.size[y]) {
[x, y] = [y, x];
}
// 把y并入x
this.parent[y] = x;
this.size[x] += this.size[y];
// 联通分量的数量减一
this.setCount -= 1;
return true;
}
// 如果 a和b 在同一个联通分量中,返回true,否则返回false
connected (a, b) {
const x = this.findset(a), y = this.findset(b);
return x === y;
}
}
思路:
将每个格子作为一个结点,格子之间的差值为边的权重,把边按权重从小到大排序。
构建并查集
按照权重从小到大,将边加入,每次检查图的联通性,让图刚好能联通的最后一条边的权重就是最小的体力消耗、
提交结果:
4 寻找两个正序数组的中位数
题目描述:
代码:
class Solution(object):
def findMedianSortedArrays(self, nums1, nums2):
def getmid(nums1, nums2):
if nums1 == [] or nums2 ==[]:
if nums1 == [] and nums2 != []:
nums = nums2
if nums2 == [] and nums1 != []:
nums = nums1
n = len(nums)
if n % 2 == 0: # 偶数个
a = nums[n//2]
b = nums[n//2 - 1]
c = float((a + b)) / 2
else:
# 奇数个数
c = nums[n//2]
return c
else:
ptr1 = 0
ptr2 = 0
m = len(nums1)
n = len(nums2)
end = (m + n ) / 2
flag = 0
tick = 0
ans = []
if (m+n) % 2 == 0: #偶数个
# end = (m + n ) / 2
flag = 1
else:
pass
while tick <= end:
if ptr1 == m or ptr2 == n:
if ptr1 == m:
ans.append(nums2[ptr2])
ptr2 += 1
if ptr2 == n and ptr1 != m:
ans.append(nums1[ptr1])
ptr1 += 1
else:
if nums2[ptr2] <= nums1[ptr1]:
ans.append(nums2[ptr2])
ptr2 +=1
else:
ans.append(nums1[ptr1])
ptr1 +=1
tick += 1
print(ans)
if flag == 1:
return float((ans[-2] + ans[-1])) / 2
else:
return ans[-1]
num = getmid(nums1, nums2)
return num
思路:
分两种情况:输入的两个数组中有一个数组为空;两个数组都不为空
第一种情况:去不为空的那个数组里找中位数即可,需要分开讨论数组长度为奇数和偶数的情况。
第二种情况:两个都不为空,用双指针的思想去维护一个新的数组,为了减少计算量, 可以提前计算出要求中位数需要计算的步长,设为end,每一步的标志设为tick,同样,要注意对长度为奇数和长度为偶数情况的讨论。
提交结果:
2 两数相加
题目描述:
代码:
/**
* Definition for singly-linked list.
* function ListNode(val, next) {
* this.val = (val===undefined ? 0 : val)
* this.next = (next===undefined ? null : next)
* }
*/
/**
* @param {ListNode} l1
* @param {ListNode} l2
* @return {ListNode}
*/
var addTwoNumbers = function(l1, l2) {
let head = null, tail = null;
let jinwei = 0
while (l1||l2){
let n1 = l1 ? l1.val : 0
let n2 = l2 ? l2.val : 0
let sum = n1 + n2 + jinwei
if (sum >= 10) jinwei = 1
else jinwei = 0
if (! head){
head = tail = new ListNode(sum % 10)
}else{
tail.next = new ListNode(sum % 10)
tail = tail.next
}
if (l1){
l1 = l1.next
}
if (l2){
l2 = l2.next
}
}
// 如果尾巴上还有一个进位的
if (jinwei > 0){
tail.next = new ListNode(jinwei)
}
return head
};
思路:
思路很简单,对应位的值相加即可,注意要维护一个进位符。主要是学习javascript的链表操作。
提交结果:
778 水位上升的泳池中游泳
题目描述:
代码:
/**
* @param {number[][]} grid
* @return {number}
*/
var swimInWater = function(grid) {
heights = grid
const m = heights.length;
const n = heights[0].length;
const edges = [];
for (let i = 0; i < m; ++i) {
for (let j = 0; j < n; ++j) {
// edges.push([i, j, heights[i][j]])
const id = i * n + j;
if (i > 0) {
edges.push([id - n, id, Math.max(heights[i][j] , heights[i - 1][j])]);
}
if (j > 0) {
edges.push([id - 1, id, Math.max(heights[i][j] , heights[i][j - 1])]);
}
}
}
// 按照list的第三项做排序(升序)
// 原本的每个空格heights[i][j] 是被作为了图G中的一个结点,结点编号为0 - (m*n -1)
edges.sort((a, b) => a[2] - b[2]);
// console.log(edges)
// new 一个并查集
const uf = new UnionFind(m * n);
let ans = 0;
// 按照权重从小到大挨个往并查集里面并入。
// 等到第0和结点和最后一个结点联通,此时的权重就是最小 体力消耗值
for (const edge of edges) {
const x = edge[0], y = edge[1], v = edge[2];
uf.unite(x, y);
if (uf.connected(0, m * n - 1)) {
ans = v;
break;
}
}
return ans;
};
// JS版本的 并查集模板
class UnionFind {
// 构造函数
constructor (n) {
this.parent = new Array(n).fill(0).map((element, index) => index);
this.size = new Array(n).fill(1);
// 当前连通分量数目
this.setCount = n;
}
// 找parent
findset (x) {
if (this.parent[x] === x) {
return x;
}
this.parent[x] = this.findset(this.parent[x]);
return this.parent[x];
}
// 将两个子集联合起来, (联通起来)
unite (a, b) {
// 找这两个子集parent, 如果parent相同,则本身就是联通的,返回false
let x = this.findset(a), y = this.findset(b);
if (x === y) {
return false;
}
// 让小的为y, 大的为x, 保证是小的并入大的
if (this.size[x] < this.size[y]) {
[x, y] = [y, x];
}
// 把y并入x
this.parent[y] = x;
this.size[x] += this.size[y];
// 联通分量的数量减一
this.setCount -= 1;
return true;
}
// 如果 a和b 在同一个联通分量中,返回true,否则返回false
connected (a, b) {
const x = this.findset(a), y = this.findset(b);
return x === y;
}
}
思路:和前面那道并查集的题思路一模一样,代码中只需要把边的权重计算时算差值改成算max值就可以了。
提交结果:
19 删除链表的倒数第 N 个结点
题目描述:
代码:
/**
* Definition for singly-linked list.
* function ListNode(val, next) {
* this.val = (val===undefined ? 0 : val)
* this.next = (next===undefined ? null : next)
* }
*/
/**
* @param {ListNode} head
* @param {number} n
* @return {ListNode}
*/
var removeNthFromEnd = function(head, n) {
nums = 0
q = head
while(q){
q = q.next
nums += 1
}
// console.log(nums)
flag = 0
end = nums - n -1
if (n == nums) return head.next
tail = head
while(tail){
if (flag == end) tail.next = tail.next.next
else tail = tail.next
flag += 1
}
return head
};
思路:
先遍历,求出链表总长度,然后计算出倒数第n位对应的是正数第几位,再次遍历去删掉那一位即可。
提交结果:
701 二叉搜索树中的插入操作
题目描述:
代码:
var insertIntoBST = function(root, val) {
let N = new TreeNode(val, null, null)
if (root == null) {
return N
}
let nowN = root
let a = true
while (a){
if (nowN){
if (nowN.val < val){
if (nowN.right != null){
nowN = nowN.right
}else{
nowN.right = N
a = false
}
}else{
if (nowN.left != null){
nowN = nowN.left
}else{
nowN.left = N
a= false
}
}
}
}
return root
};
思路:
根据二叉搜索树的性质(比该节点小的值应该在其左子树中,比该节点大的值在右子树中),迭代搜索找到该结点应该插入的位置。
提交结果:
725 分隔链表
题目描述:
代码:
/**
* Definition for singly-linked list.
* function ListNode(val) {
* this.val = val;
* this.next = null;
* }
*/
/**
* @param {ListNode} root
* @param {number} k
* @return {ListNode[]}
*/
var splitListToParts = function(root, k) {
// 先要直到链表有多长
let q = root
flag = 0
while(q){
flag += 1
q = q.next
}
// console.log(flag)
let num1 = Math.floor(flag / k)
let num2 = flag % k
let ans = []
for (let i=0;i 0) ans.push(num1 + 1)
else ans.push(num1)
num2 -= 1
}
let out = []
// console.log(ans)
let tail = root
for(var i=0;i 0){
p = p.next
num -= 1
}
if (p==null) ppp = null
else
{
ppp = p.next
p.next = null
}
out.push(tail)
console.log(out)
tail = ppp
}
return out
// return 0
};
思路:先要求出链表有多长,然后用一个ans数组来存储分离信息。len(ans)代表了分成几段,ans[i]代表第 i 段有多少个元素。然后遍历ans数组来实施分段。
提交结果:
328 奇偶链表
题目描述:
代码:
/**
* Definition for singly-linked list.
* function ListNode(val, next) {
* this.val = (val===undefined ? 0 : val)
* this.next = (next===undefined ? null : next)
* }
*/
/**
* @param {ListNode} head
* @return {ListNode}
*/
var oddEvenList = function(head) {
flag = 0
if (! head) return head
k = new ListNode(head.val, null)
k_tail = k
tail = head.next
while(tail){
if (flag == 0){
tail = tail.next
flag = 1
}else{
q = new ListNode(tail.val, null)
k_tail.next = q
tail = tail.next
k_tail = k_tail.next
flag = 0
}
}
ans = 0
while(head){
if (ans == 0){
head = head.next
ans = 1
}else{
q = new ListNode(head.val, null)
k_tail.next = q
head = head.next
k_tail = k_tail.next
ans = 0
}
}
return k
};
思路:先遍历取出奇数结点,再遍历取出偶数结点(还没有满足题中让尝试的要求)
提交结果:
94 二叉树的中序遍历(后序遍历类似)
题目描述:代码:
/**
* Definition for a binary tree node.
* function TreeNode(val, left, right) {
* this.val = (val===undefined ? 0 : val)
* this.left = (left===undefined ? null : left)
* this.right = (right===undefined ? null : right)
* }
*/
/**
* @param {TreeNode} root
* @return {number[]}
*/
var inorderTraversal = function(root) {
if (! root) return []
ans = []
function midB(root){
if (root){
midB(root.left)
ans.push(root.val)
midB(root.right)
}
return
}
midB (root)
return ans
};
思路:
递归,左中右的顺序读取值、
提交结果:
论文阅读
Dual Attention Network for Scene Segmentation
出自 : cvpr 2019
任务背景:
为实现更好的场景分割。因为对象的比例、遮挡、光照变化等因素会造成场景分割的困难。
现状:
现在常用全卷积神经网络来处理语义分割任务。为提升分割准确性,常见的思路有:
1、多尺度方法(有助于捕捉不同比例的对象,但它不能在全局视图中利用对象或素材之间的关系)
2、编-解码形状网络
3、利用循环神经网络做语义分割(严重依赖长期记忆的学习结果)
本文创新点:
提出了一种双注意力网络(DA-net),包括位置注意力模块(Position Attention Module)和通道注意力模块(Channel Attention Module)。
位置注意力模块:引入了自我注意机制来捕捉特征地图任意两个位置之间的空间相关性。
通道注意力模块:使用自我注意机制来捕获任意两个频道映射之间的频道依赖关系
贡献:
1、提出引入双注意力机制的DA-net
2、提出了位置注意模块来学习特征的空间相关性,并设计了通道注意模块来建模通道相关性。它通过对局部特征的丰富上下文依赖性建模,显著改善了分割结果。
3、在Cityscapes、PASCAL Context 和 COCO Stuff 数据集上取得了最好的效果。
网络结构:
概览:
可见,DA-net主要由三部分组成:
1、主干网络 2、空间注意力模块(PAM) 3、通道注意力模块(CAM)
其中,PAM模块详细结构如下图所示:
过程描述:
start
give input --> A(C * H * W)
use 1 * 1 conv get B, C ,D
B (C * H * W) reshape --> to B(C * N) while N = H * W
C (C * H * W) reshape --> to C(C * N) while N = H * W
D (C * H * W) reshape --> to D(C * N) while N = H * W
Bt (N * C) = transpose(B)
S (N * N) = softmax( Bt * C )
St (N * N) = transpose(S)
D = D * St
D (C * N) reshape --> to D(C * H * W)
E = A + a * D (a 是加权值,a是可训练参数,初始值为0)
output E
end
CAM模块结构如下:
CAM模块的实现和PAM的类似,不过要注意它没有1 * 1卷积
作者实验:
作者分别用实验讨论了PAM模块和CAM模块的作用
PAM模块有助于增强细节的区分度
CAM模块有助于提升语义的一致性,增加分类的正确性
可以发现:空间注意子图总能关注到与该点同类的物体的位置;而不同的通道则关注了不同的重点。
PAM模块和CAM模块的作用从评价指标上也可以得到反映:
在各数据集上的最终结果:
我的实验
对OCTA-500数据集,做了以下尝试:
1、只使用oct模态,将三维输入数据的height维度当做channel,利用u-net网络跑了一下看效果。得到的输出只能分割出FAZ(视网膜中央凹无血管区),无法分割出RV(视网膜血管),且分割出来的FAZ指标也不好。猜测的原因可能有:1)二维卷积对于空间信息的利用不够导致分不出血管。 2)数据集作者在做3分类时,采用的是两次二分类,而非直接做的多分类,可能原作者也遇到了直接多分类比较困难的问题。 3)可能数据处理阶段有代码写错了,不过因为输入是三维的tensor,通过打印中间过程来排查不好观察。
2、结合oct模态和octa模态,让两种数据以两个channel的形式输入,用作者论文中的IPN网络做实验,因为3D卷积资源消耗大,会导致爆显存,原作者将长和宽从400 * 400裁剪为了100 * 100。裁剪方式用了两种:1)、将每个400 * 400的图裁成16张100 * 100的图,分别存储裁剪后的子图,原数据集扩充16倍。2)、为每张图生成随机值,依据随机值做裁剪,每张大图只生成一张裁剪后的小图,通过增加epoch轮数来增加迭代次数。 还没有跑出效果来,还在找问题。
后续安排
1、继续看论文,大致方向是3D分割方面的,比如3D-U-net和V-net这类型的
2、装tensorflow环境把作者的代码跑起来先看看效果再考虑用pytorch复现和修改网络结构
3、leetcode作题目,最近打算多做一些数据结构相关的题目
(远程连接做实验有些不稳定,本来打算截些实验过程的图,又突然连不上了)